From 69cb115673fdc39af464295ad7d13f26264c540a Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Sun, 29 Dec 2024 00:28:21 +0530 Subject: [PATCH 01/23] implement real time suggestion --- frontend/package-lock.json | 120 +++++++++++++++++++++ frontend/package.json | 3 + frontend/src/components/AutoSuggestion.tsx | 63 +++++++++++ frontend/src/components/Search.tsx | 54 ++-------- 4 files changed, 193 insertions(+), 47 deletions(-) create mode 100644 frontend/src/components/AutoSuggestion.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 528b8a58a..953488549 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,6 +34,9 @@ "tailwind-merge": "^2.5.4" }, "devDependencies": { + "@algolia/autocomplete-js": "^1.17.8", + "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", + "@algolia/autocomplete-theme-classic": "^1.17.8", "@eslint/js": "^9.15.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", @@ -84,6 +87,97 @@ "dev": true, "license": "MIT" }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.8.tgz", + "integrity": "sha512-WKQS6+83DYB7bsCN7HUxPGzwR1ZtKy+E8WybnuxnsGzQG/zd4j96R4USqsjz0/tOQVAA4hnlqNn9LtcaOIjCyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.8", + "@algolia/autocomplete-shared": "1.17.8" + } + }, + "node_modules/@algolia/autocomplete-js": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-js/-/autocomplete-js-1.17.8.tgz", + "integrity": "sha512-WQ5xZ5sY16UH6V0+ACpe2NoKPWs9L/m5uQQn8eNiM7jsHShonEhwV3/kB897k1TovfHqhtH0gPXXMN95taZAqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.8", + "@algolia/autocomplete-preset-algolia": "1.17.8", + "@algolia/autocomplete-shared": "1.17.8", + "htm": "^3.1.1", + "preact": "^10.13.2" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.5.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.8.tgz", + "integrity": "sha512-aNbLpDONZejsQKdozt0c6mFvUc8yINpv6WgHyJ9oZm4GFkwtbe0KWTlowyNIO/yRaoGC+Y0BkmSLMnGImy01eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.8" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-plugin-query-suggestions": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-query-suggestions/-/autocomplete-plugin-query-suggestions-1.17.8.tgz", + "integrity": "sha512-6ZWPjvkL+0SOqKIURTVkdV3p4nLHm11ei+3ra2O2yaZ9ESZoE7nIfjTdOyEMEm00ocVLzVXnAVlB/rZ69s89UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.8", + "@algolia/autocomplete-js": "1.17.8", + "@algolia/autocomplete-preset-algolia": "1.17.8", + "@algolia/autocomplete-shared": "1.17.8" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.5.1 < 6", + "algoliasearch": ">= 4.5.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.8.tgz", + "integrity": "sha512-EHGmvfV9Y6HzDlTSt/AAdOthVTH8zgr6A4h9ehheDsAjqsyXj9uNvMAd4lq5bOJs+MZCWTcxpK+Btr9Tcihr3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.8" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.8.tgz", + "integrity": "sha512-2w26RGB9zlyrnPpT/TpB0pbtukmUVYrKVt9ydlHnpPU7FgoiblUBpzzQTVzqMqYASGGjURaKLu7FakmaTIkClg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-theme-classic": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.17.8.tgz", + "integrity": "sha512-70IJMktChk6mFTqegBYZk5MR0vquX+JEUG7rc4Jo75LTbe3BN4Lh6nUwaBXoLpx4v1zMID1vOsJ0Zv1M/3q3Jw==", + "dev": true, + "license": "MIT" + }, "node_modules/@algolia/client-abtesting": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.18.0.tgz", @@ -6365,6 +6459,13 @@ "react-is": "^16.7.0" } }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -9708,6 +9809,17 @@ "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.25.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", + "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10439,6 +10551,14 @@ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 97cc2914a..8ea5fbd53 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,9 @@ "tailwind-merge": "^2.5.4" }, "devDependencies": { + "@algolia/autocomplete-js": "^1.17.8", + "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", + "@algolia/autocomplete-theme-classic": "^1.17.8", "@eslint/js": "^9.15.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx new file mode 100644 index 000000000..39a3953a6 --- /dev/null +++ b/frontend/src/components/AutoSuggestion.tsx @@ -0,0 +1,63 @@ +import { autocomplete } from '@algolia/autocomplete-js' +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions' +import '@algolia/autocomplete-theme-classic' +import { debounce } from 'lodash' +import { useEffect, useRef } from 'react' +import { client } from '../lib/algoliaClient' + +interface SearchProps { + // eslint-disable-next-line no-unused-vars + onChange: (query: string) => void + placeholder: string + initialValue?: string +} + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient: client, + indexName: 'issue_suggestions', + getSearchParams: () => ({ + hitsPerPage: 10, + }), +}) + +const Autocomplete = ({ onChange, placeholder, initialValue = '' }: SearchProps) => { + const containerRef = useRef(null) + + const debouncedOnChange = useRef( + debounce((query: string) => { + onChange(query) + }, 1000) + ).current + useEffect(() => { + return () => { + debouncedOnChange.cancel() + } + }, [debouncedOnChange]) + useEffect(() => { + if (containerRef.current) { + const search = autocomplete({ + container: containerRef.current, + placeholder, + openOnFocus: true, + plugins: [querySuggestionsPlugin], + initialState: { query: initialValue }, + onStateChange: ({ state }) => { + const { query } = state + debouncedOnChange(query as string) + }, + }) + + return () => { + search.destroy() + } + } + }, [onChange, placeholder, initialValue, debouncedOnChange]) + + return ( +
+
+
+ ) +} + +export default Autocomplete diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index fd1b9bb0a..0f5cf34b7 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,6 +1,5 @@ -import { debounce } from 'lodash' -import { Search, X } from 'lucide-react' -import React, { useEffect, useRef, useState, useMemo } from 'react' +import React, { useState } from 'react' +import Autocomplete from './AutoSuggestion' interface SearchProps { /* eslint-disable-next-line */ @@ -11,59 +10,20 @@ interface SearchProps { const SearchComponent: React.FC = ({ onSearch, placeholder, initialValue = '' }) => { const [searchQuery, setSearchQuery] = useState(initialValue) - const inputRef = useRef(null) - useEffect(() => { - setSearchQuery(initialValue) - }, [initialValue]) - - useEffect(() => { - inputRef.current?.focus() - }, []) - - const debouncedSearch = useMemo( - () => debounce((query: string) => onSearch(query), 750), - [onSearch] - ) - - useEffect(() => { - return () => { - debouncedSearch.cancel() - } - }, [debouncedSearch]) - - const handleSearchChange = (e: React.ChangeEvent) => { - const newQuery = e.target.value - setSearchQuery(newQuery) - debouncedSearch(newQuery) - } - - const handleClearSearch = () => { - setSearchQuery('') - onSearch('') - inputRef.current?.focus() + const handleSearchChange = (query: string) => { + setSearchQuery(query) + onSearch(query) } return (
- - - {searchQuery && ( - - )}
) From 871905f9f96710e804ea78603dd53392f05ad27c Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Sun, 29 Dec 2024 17:28:28 +0530 Subject: [PATCH 02/23] optimization and css changes --- frontend/src/components/AutoSuggestion.tsx | 94 +++++++++++--------- frontend/src/components/Autosuggestion.css | 90 +++++++++++++++++++ frontend/src/components/Search.tsx | 28 ++++-- frontend/src/components/SearchPageLayout.tsx | 50 +++++------ frontend/src/pages/Chapters.tsx | 1 + frontend/src/pages/Committees.tsx | 1 + frontend/src/pages/Contribute.tsx | 1 + frontend/src/pages/Projects.tsx | 1 + 8 files changed, 189 insertions(+), 77 deletions(-) create mode 100644 frontend/src/components/Autosuggestion.css diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index 39a3953a6..9c940f589 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -2,62 +2,74 @@ import { autocomplete } from '@algolia/autocomplete-js' import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions' import '@algolia/autocomplete-theme-classic' import { debounce } from 'lodash' +import React from 'react' import { useEffect, useRef } from 'react' import { client } from '../lib/algoliaClient' +import './Autosuggestion.css' interface SearchProps { // eslint-disable-next-line no-unused-vars onChange: (query: string) => void placeholder: string initialValue?: string + indexName: string + onReady: () => void } -const querySuggestionsPlugin = createQuerySuggestionsPlugin({ - searchClient: client, - indexName: 'issue_suggestions', - getSearchParams: () => ({ - hitsPerPage: 10, - }), -}) +const Autocomplete = React.memo( + ({ onChange, placeholder, initialValue = '', indexName, onReady }: SearchProps) => { + const containerRef = useRef(null) + const searchRef = useRef | null>(null) -const Autocomplete = ({ onChange, placeholder, initialValue = '' }: SearchProps) => { - const containerRef = useRef(null) - - const debouncedOnChange = useRef( - debounce((query: string) => { + const debouncedOnChange = debounce((query: string) => { onChange(query) }, 1000) - ).current - useEffect(() => { - return () => { - debouncedOnChange.cancel() - } - }, [debouncedOnChange]) - useEffect(() => { - if (containerRef.current) { - const search = autocomplete({ - container: containerRef.current, - placeholder, - openOnFocus: true, - plugins: [querySuggestionsPlugin], - initialState: { query: initialValue }, - onStateChange: ({ state }) => { - const { query } = state - debouncedOnChange(query as string) - }, - }) + useEffect(() => { return () => { - search.destroy() + debouncedOnChange.cancel() } - } - }, [onChange, placeholder, initialValue, debouncedOnChange]) - - return ( -
-
-
- ) -} + }, [debouncedOnChange]) + + useEffect(() => { + if (containerRef.current) { + const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient: client, + indexName: indexName, + getSearchParams: () => ({ + hitsPerPage: 7, + }), + }) + + searchRef.current = autocomplete({ + container: containerRef.current, + placeholder, + autoFocus: true, + openOnFocus: true, + insights: true, + plugins: [querySuggestionsPlugin], + initialState: { query: initialValue }, + onStateChange: ({ state }) => { + const { query } = state + debouncedOnChange(query as string) + }, + }) + onReady() + return () => { + if (searchRef.current) { + searchRef.current.destroy() + } + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexName]) + + return ( +
+
+
+ ) + } +) export default Autocomplete diff --git a/frontend/src/components/Autosuggestion.css b/frontend/src/components/Autosuggestion.css new file mode 100644 index 000000000..91a387db5 --- /dev/null +++ b/frontend/src/components/Autosuggestion.css @@ -0,0 +1,90 @@ +:root { + --aa-search-input-height: 44px; + --aa-input-icon-size: 20px; + + --aa-base-unit: 16; + --aa-spacing-factor: 1; + --aa-spacing: calc(var(--aa-base-unit) * var(--aa-spacing-factor) * 1px); + --aa-spacing-half: calc(var(--aa-spacing) / 2); + --aa-panel-max-height: 650px; + + --aa-base-z-index: 9999; + + --aa-font-size: calc(var(--aa-base-unit) * 1px); + --aa-font-family: inherit; + --aa-font-weight-medium: 500; + --aa-font-weight-semibold: 600; + --aa-font-weight-bold: 700; + + --aa-icon-size: 20px; + --aa-icon-stroke-width: 1.6; + --aa-icon-color-rgb: 0, 0, 0; + --aa-icon-color-alpha: 0.6; + --aa-action-icon-size: 20px; + + --aa-text-color-rgb: 38, 38, 39; + --aa-text-color-alpha: 1; + --aa-primary-color-rgb: 152, 175, 199; + --aa-primary-color-alpha: 0.4; + --aa-muted-color-rgb: 128, 126, 163; + --aa-muted-color-alpha: 0.6; + + --aa-panel-border-color-rgb: 128, 126, 163; + --aa-panel-border-color-alpha: 0.3; + --aa-input-border-color-rgb: 128, 126, 163; + --aa-input-border-color-alpha: 0.8; + + --aa-background-color-rgb: 255, 255, 255; + --aa-background-color-alpha: 1; + --aa-input-background-color-rgb: 255, 255, 255; + --aa-input-background-color-alpha: 1; + --aa-selected-color-rgb: 152, 175, 199; + --aa-selected-color-alpha: 0.45; + --aa-description-highlight-background-color-rgb: 245, 223, 77; + --aa-description-highlight-background-color-alpha: 0.5; + + --aa-detached-media-query: (max-width: 680px); + --aa-detached-modal-media-query: (min-width: 680px); + --aa-detached-modal-max-width: 680px; + --aa-detached-modal-max-height: 500px; + --aa-overlay-color-rgb: 115, 114, 129; + --aa-overlay-color-alpha: 0.4; + + --aa-panel-shadow: 0 0 0 1px rgba(35, 38, 59, 0.1), 0 6px 16px -4px rgba(35, 38, 59, 0.15); + + --aa-scrollbar-width: 13px; + --aa-scrollbar-track-background-color-rgb: 234, 234, 234; + --aa-scrollbar-track-background-color-alpha: 1; + --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); + --aa-scrollbar-thumb-background-color-alpha: 1; + + @media (hover: none) and (pointer: coarse) { + --aa-spacing-factor: 1.2; + --aa-action-icon-size: 22px; + } +} + +body { + + &[data-theme='dark'], + &.dark { + --aa-text-color-rgb: 183, 192, 199; + --aa-primary-color-rgb: 219, 221, 224; + --aa-primary-color-alpha: 0.1; + --aa-muted-color-rgb: 219, 221, 224; + + --aa-input-background-color-rgb: 60, 60, 60; + --aa-background-color-rgb: 50, 50, 50; + --aa-selected-color-rgb: 128, 128, 128; + --aa-selected-color-alpha: 0.25; + --aa-description-highlight-background-color-rgb: 0, 255, 255; + --aa-description-highlight-background-color-alpha: 0.25; + + --aa-icon-color-rgb: 255, 255, 255; + + --aa-panel-shadow: inset 1px 1px 0 0 rgb(58, 59, 63), 0 3px 8px 0 rgb(37, 35, 35); + + --aa-scrollbar-track-background-color-rgb: 44, 46, 64; + --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); + } +} \ No newline at end of file diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 0f5cf34b7..92871f9f2 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useCallback } from 'react' import Autocomplete from './AutoSuggestion' interface SearchProps { @@ -6,23 +6,33 @@ interface SearchProps { onSearch: (query: string) => void placeholder: string initialValue?: string + indexName: string + onReady: () => void } -const SearchComponent: React.FC = ({ onSearch, placeholder, initialValue = '' }) => { - const [searchQuery, setSearchQuery] = useState(initialValue) - - const handleSearchChange = (query: string) => { - setSearchQuery(query) - onSearch(query) - } +const SearchComponent: React.FC = ({ + onSearch, + placeholder, + initialValue = '', + indexName = 'issue_suggestions', + onReady, +}) => { + const handleSearchChange = useCallback( + (query: string) => { + onSearch(query) + }, + [onSearch] + ) return (
diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx index c165e61f3..8569ad24e 100644 --- a/frontend/src/components/SearchPageLayout.tsx +++ b/frontend/src/components/SearchPageLayout.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from 'react' - +import React, { useState } from 'react' import LoadingSpinner from 'components/LoadingSpinner' import Pagination from 'components/Pagination' import SearchBar from 'components/Search' @@ -9,12 +8,13 @@ interface SearchPageLayoutProps { totalPages: number currentPage: number searchQuery: string - /* eslint-disable-next-line */ + // eslint-disable-next-line no-unused-vars onSearch: (query: string) => void - /* eslint-disable-next-line */ + // eslint-disable-next-line no-unused-vars onPageChange: (page: number) => void searchPlaceholder: string empty: string + indexName: string loadingImageUrl?: string children?: React.ReactNode } @@ -28,40 +28,36 @@ const SearchPageLayout = ({ onPageChange, searchPlaceholder, empty, + indexName, loadingImageUrl = '/img/owasp_icon_white_sm.png', children, }: SearchPageLayoutProps) => { - const [isFirstLoad, setIsFirstLoad] = useState(true) + const [isSearchBarReady, setIsSearchBarReady] = useState(false) - useEffect(() => { - if (isLoaded && isFirstLoad) { - setIsFirstLoad(false) - } - }, [isLoaded, isFirstLoad]) + const handleSearchBarReady = () => { + setIsSearchBarReady(true) + } return (
- {isFirstLoad ? ( + {!isLoaded && !isSearchBarReady && (
- ) : ( + )} +
+ +
+ {isLoaded && isSearchBarReady && (
- - {!isLoaded ? ( -
- -
- ) : ( -
- {totalPages === 0 &&
{empty}
} - {children} -
- )} + {totalPages === 0 &&
{empty}
} + {children} {totalPages > 1 && ( { totalPages={totalPages} currentPage={currentPage} searchQuery={searchQuery} + indexName="chapters" onSearch={handleSearch} onPageChange={handlePageChange} searchPlaceholder="Search for OWASP chapters..." diff --git a/frontend/src/pages/Committees.tsx b/frontend/src/pages/Committees.tsx index 5fd7fbf32..8c5e4f9ae 100644 --- a/frontend/src/pages/Committees.tsx +++ b/frontend/src/pages/Committees.tsx @@ -50,6 +50,7 @@ const CommitteesPage = () => { return ( { return ( { totalPages={totalPages} currentPage={currentPage} searchQuery={searchQuery} + indexName="projects" onSearch={handleSearch} onPageChange={handlePageChange} empty="No projects found" From 1b4c443e273b73fe06981b1eef9a303100aaea6b Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Sun, 29 Dec 2024 17:32:16 +0530 Subject: [PATCH 03/23] pre_commit --- frontend/src/components/Autosuggestion.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Autosuggestion.css b/frontend/src/components/Autosuggestion.css index 91a387db5..cb3678dbf 100644 --- a/frontend/src/components/Autosuggestion.css +++ b/frontend/src/components/Autosuggestion.css @@ -87,4 +87,4 @@ body { --aa-scrollbar-track-background-color-rgb: 44, 46, 64; --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); } -} \ No newline at end of file +} From 6253fc4a8d4c660733d32f256106ab12e7a25082 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Sun, 29 Dec 2024 22:22:46 +0530 Subject: [PATCH 04/23] added jest config --- frontend/jest.config.ts | 1 + frontend/jest.setup.ts | 16 +++++++++++++ frontend/package-lock.json | 21 +++++++++++++++++ frontend/package.json | 1 + frontend/src/components/Autosuggestion.css | 1 - frontend/src/components/SearchPageLayout.tsx | 24 ++++++++++++-------- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 261dc969f..3c6f2b8df 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -33,6 +33,7 @@ export default { moduleNameMapper: { '^@tests/(.*)$': '/__tests__/src/$1', '^@/(.*)$': '/src/$1', + '\\.(scss|sass|css)$': 'identity-obj-proxy', }, moduleDirectories: ['node_modules', 'src'], } diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index db9c982bf..f8da2e30c 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -1,4 +1,20 @@ import '@testing-library/jest-dom' import { TextEncoder } from 'util' +beforeEach(() => { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }) +}) +jest.mock('@algolia/autocomplete-theme-classic', () => ({})) global.TextEncoder = TextEncoder diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 953488549..a873f840b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -60,6 +60,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.14.0", "husky": "^9.1.7", + "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.2.11", @@ -6357,6 +6358,13 @@ "dev": true, "license": "MIT" }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true, + "license": "(Apache-2.0 OR MPL-1.1)" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -6554,6 +6562,19 @@ "node": ">=0.10.0" } }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8ea5fbd53..c33dd3fb8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -70,6 +70,7 @@ "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.14.0", "husky": "^9.1.7", + "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.2.11", diff --git a/frontend/src/components/Autosuggestion.css b/frontend/src/components/Autosuggestion.css index cb3678dbf..17d3755e3 100644 --- a/frontend/src/components/Autosuggestion.css +++ b/frontend/src/components/Autosuggestion.css @@ -65,7 +65,6 @@ } body { - &[data-theme='dark'], &.dark { --aa-text-color-rgb: 183, 192, 199; diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx index 8569ad24e..5eafecd99 100644 --- a/frontend/src/components/SearchPageLayout.tsx +++ b/frontend/src/components/SearchPageLayout.tsx @@ -54,20 +54,24 @@ const SearchPageLayout = ({ onReady={handleSearchBarReady} />
- {isLoaded && isSearchBarReady && ( -
+ {!isLoaded ? ( +
+ +
+ ) : ( +
{totalPages === 0 &&
{empty}
} {children} - {totalPages > 1 && ( - - )}
)} + {totalPages > 1 && ( + + )}
) } From f4fcb53064aa4b11fe6cd008f460c35fd9564617 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Sun, 29 Dec 2024 23:04:44 +0530 Subject: [PATCH 05/23] updated jest config --- frontend/__tests__/src/pages/Contribute.test.tsx | 8 -------- frontend/jest.setup.ts | 2 ++ frontend/package-lock.json | 13 +++++++++++++ frontend/package.json | 1 + 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/__tests__/src/pages/Contribute.test.tsx b/frontend/__tests__/src/pages/Contribute.test.tsx index 7f657de4d..fd1d3e4bf 100644 --- a/frontend/__tests__/src/pages/Contribute.test.tsx +++ b/frontend/__tests__/src/pages/Contribute.test.tsx @@ -13,14 +13,6 @@ jest.mock('lib/api', () => ({ fetchAlgoliaData: jest.fn(), })) -jest.mock('lib/utils', () => ({ - getFilteredIcons: jest.fn(), -})) - -jest.mock('utils/credentials', () => ({ - API_URL: 'https://mock-api.com', -})) - jest.mock('components/Pagination', () => jest.fn(({ currentPage, onPageChange, totalPages }) => totalPages > 1 ? ( diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index f8da2e30c..27b0432fa 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -1,5 +1,7 @@ import '@testing-library/jest-dom' import { TextEncoder } from 'util' +import dotenv from 'dotenv' +dotenv.config() beforeEach(() => { Object.defineProperty(window, 'matchMedia', { writable: true, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a873f840b..72d7abd46 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "clsx": "^2.1.1", "dayjs": "^1.11.13", "dompurify": "^3.2.3", + "dotenv": "^16.4.7", "lodash": "^4.17.21", "lucide-react": "^0.469.0", "markdown-it": "^14.1.0", @@ -4902,6 +4903,18 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index c33dd3fb8..5d7296461 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "clsx": "^2.1.1", "dayjs": "^1.11.13", "dompurify": "^3.2.3", + "dotenv": "^16.4.7", "lodash": "^4.17.21", "lucide-react": "^0.469.0", "markdown-it": "^14.1.0", From b0a1719cad86f7e4533ff91c58e29b031011d72a Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Sun, 29 Dec 2024 10:38:18 -0800 Subject: [PATCH 06/23] Add env-file --- frontend/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/Makefile b/frontend/Makefile index f14a0ba29..36f25b921 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -18,4 +18,4 @@ shell-frontend: test-frontend: @docker build -f frontend/Dockerfile.test frontend -t nest-frontend-test - @docker run nest-frontend-test npm run test + @docker run --env-file frontend/.env.example nest-frontend-test npm run test From fe179397d11bbd0853b9b1e11dcf0a08ca4c3829 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Mon, 30 Dec 2024 00:36:05 +0530 Subject: [PATCH 07/23] updated test case --- frontend/__tests__/src/pages/Chapters.test.tsx | 4 +--- frontend/__tests__/src/pages/Committees.test.tsx | 5 +---- frontend/__tests__/src/pages/Projects.test.tsx | 2 -- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx index 7ff1d961c..d707157fd 100644 --- a/frontend/__tests__/src/pages/Chapters.test.tsx +++ b/frontend/__tests__/src/pages/Chapters.test.tsx @@ -78,7 +78,7 @@ describe('ChaptersPage Component', () => { }) }) - test('renders SearchBar, data, and pagination component concurrently after data is loaded', async () => { + test('renders data, and pagination component concurrently after data is loaded', async () => { window.scrollTo = jest.fn() ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({ hits: mockChapterData.chapters, @@ -89,12 +89,10 @@ describe('ChaptersPage Component', () => { const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { expect(loadingSpinner.length).toBeGreaterThan(0) - expect(screen.queryByPlaceholderText('Search for OWASP chapters...')).not.toBeInTheDocument() expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) await waitFor(() => { expect(screen.getByPlaceholderText('Search for OWASP chapters...')).toBeInTheDocument() - expect(screen.getByPlaceholderText('Search for OWASP chapters...')).toHaveFocus() expect(screen.getByText('Chapter 1')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) diff --git a/frontend/__tests__/src/pages/Committees.test.tsx b/frontend/__tests__/src/pages/Committees.test.tsx index 0ed41a13c..b48efa2ec 100644 --- a/frontend/__tests__/src/pages/Committees.test.tsx +++ b/frontend/__tests__/src/pages/Committees.test.tsx @@ -55,15 +55,12 @@ describe('Committees Component', () => { const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { expect(loadingSpinner.length).toBeGreaterThan(0) - expect( - screen.queryByPlaceholderText('Search for OWASP committees...') - ).not.toBeInTheDocument() + expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) await waitFor(() => { expect(screen.getByPlaceholderText('Search for OWASP committees...')).toBeInTheDocument() - expect(screen.getByPlaceholderText('Search for OWASP committees...')).toHaveFocus() expect(screen.getByText('Committee 1')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) diff --git a/frontend/__tests__/src/pages/Projects.test.tsx b/frontend/__tests__/src/pages/Projects.test.tsx index 951c19c77..2a988e746 100644 --- a/frontend/__tests__/src/pages/Projects.test.tsx +++ b/frontend/__tests__/src/pages/Projects.test.tsx @@ -53,12 +53,10 @@ describe('ProjectPage Component', () => { const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { expect(loadingSpinner.length).toBeGreaterThan(0) - expect(screen.queryByPlaceholderText('Search for OWASP projects...')).not.toBeInTheDocument() expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) await waitFor(() => { expect(screen.getByPlaceholderText('Search for OWASP projects...')).toBeInTheDocument() - expect(screen.getByPlaceholderText('Search for OWASP projects...')).toHaveFocus() expect(screen.getByText('Project 1')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) From c998e0c301fd0db2c7aa4931fbeb7f033e789332 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Mon, 30 Dec 2024 16:10:28 +0530 Subject: [PATCH 08/23] updated indexName for suggestions --- frontend/src/components/AutoSuggestion.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index 9c940f589..735327370 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -6,6 +6,7 @@ import React from 'react' import { useEffect, useRef } from 'react' import { client } from '../lib/algoliaClient' import './Autosuggestion.css' +import { NEST_ENV } from 'utils/credentials' interface SearchProps { // eslint-disable-next-line no-unused-vars @@ -35,7 +36,7 @@ const Autocomplete = React.memo( if (containerRef.current) { const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient: client, - indexName: indexName, + indexName: `${NEST_ENV}_${indexName}_suggestions`, getSearchParams: () => ({ hitsPerPage: 7, }), From bd418ad22b428310108d594b39b1bdcc15b71299 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Mon, 30 Dec 2024 16:13:33 +0530 Subject: [PATCH 09/23] fix lint --- frontend/src/components/AutoSuggestion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index 735327370..e6a2c74c7 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -4,9 +4,9 @@ import '@algolia/autocomplete-theme-classic' import { debounce } from 'lodash' import React from 'react' import { useEffect, useRef } from 'react' +import { NEST_ENV } from 'utils/credentials' import { client } from '../lib/algoliaClient' import './Autosuggestion.css' -import { NEST_ENV } from 'utils/credentials' interface SearchProps { // eslint-disable-next-line no-unused-vars From 1b0a696e6dcdd4d9bc74dba9fdedee7d8f9ed959 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Mon, 30 Dec 2024 22:07:29 +0530 Subject: [PATCH 10/23] updated --- frontend/src/components/AutoSuggestion.tsx | 1 - frontend/src/components/SearchPageLayout.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index e6a2c74c7..781746ae2 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -9,7 +9,6 @@ import { client } from '../lib/algoliaClient' import './Autosuggestion.css' interface SearchProps { - // eslint-disable-next-line no-unused-vars onChange: (query: string) => void placeholder: string initialValue?: string diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx index 5eafecd99..7b3de9736 100644 --- a/frontend/src/components/SearchPageLayout.tsx +++ b/frontend/src/components/SearchPageLayout.tsx @@ -8,9 +8,9 @@ interface SearchPageLayoutProps { totalPages: number currentPage: number searchQuery: string - // eslint-disable-next-line no-unused-vars + onSearch: (query: string) => void - // eslint-disable-next-line no-unused-vars + onPageChange: (page: number) => void searchPlaceholder: string empty: string From b007c09f8ef38b6e2f13a9a729459dae7b573264 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Tue, 31 Dec 2024 15:36:15 +0530 Subject: [PATCH 11/23] fixes --- frontend/jest.setup.ts | 3 +- frontend/package-lock.json | 16 ++-------- frontend/package.json | 6 ++-- frontend/src/components/AutoSuggestion.tsx | 5 ++- frontend/src/components/Search.tsx | 2 +- frontend/src/components/SearchPageLayout.tsx | 33 +++++++++----------- 6 files changed, 26 insertions(+), 39 deletions(-) diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index 27b0432fa..f2fbacaca 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -18,5 +18,6 @@ beforeEach(() => { }) }) jest.mock('@algolia/autocomplete-theme-classic', () => ({})) - +import React from 'react' +global.React = React global.TextEncoder = TextEncoder diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dee73b09..f6b2d35a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "owasp-nest-frontend", "version": "0.0.1", "dependencies": { + "@algolia/autocomplete-js": "^1.17.8", + "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", + "@algolia/autocomplete-theme-classic": "^1.17.8", "@fortawesome/fontawesome-svg-core": "^6.7.0", "@fortawesome/free-brands-svg-icons": "^6.7.0", "@fortawesome/free-regular-svg-icons": "^6.7.0", @@ -35,9 +38,6 @@ "tailwind-merge": "^2.5.4" }, "devDependencies": { - "@algolia/autocomplete-js": "^1.17.8", - "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", - "@algolia/autocomplete-theme-classic": "^1.17.8", "@eslint/js": "^9.15.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", @@ -93,7 +93,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.8.tgz", "integrity": "sha512-WKQS6+83DYB7bsCN7HUxPGzwR1ZtKy+E8WybnuxnsGzQG/zd4j96R4USqsjz0/tOQVAA4hnlqNn9LtcaOIjCyQ==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.17.8", @@ -104,7 +103,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-js/-/autocomplete-js-1.17.8.tgz", "integrity": "sha512-WQ5xZ5sY16UH6V0+ACpe2NoKPWs9L/m5uQQn8eNiM7jsHShonEhwV3/kB897k1TovfHqhtH0gPXXMN95taZAqA==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.17.8", @@ -122,7 +120,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.8.tgz", "integrity": "sha512-aNbLpDONZejsQKdozt0c6mFvUc8yINpv6WgHyJ9oZm4GFkwtbe0KWTlowyNIO/yRaoGC+Y0BkmSLMnGImy01eQ==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.8" @@ -135,7 +132,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-query-suggestions/-/autocomplete-plugin-query-suggestions-1.17.8.tgz", "integrity": "sha512-6ZWPjvkL+0SOqKIURTVkdV3p4nLHm11ei+3ra2O2yaZ9ESZoE7nIfjTdOyEMEm00ocVLzVXnAVlB/rZ69s89UQ==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.17.8", @@ -152,7 +148,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.8.tgz", "integrity": "sha512-EHGmvfV9Y6HzDlTSt/AAdOthVTH8zgr6A4h9ehheDsAjqsyXj9uNvMAd4lq5bOJs+MZCWTcxpK+Btr9Tcihr3g==", - "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.8" @@ -166,7 +161,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.8.tgz", "integrity": "sha512-2w26RGB9zlyrnPpT/TpB0pbtukmUVYrKVt9ydlHnpPU7FgoiblUBpzzQTVzqMqYASGGjURaKLu7FakmaTIkClg==", - "dev": true, "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -177,7 +171,6 @@ "version": "1.17.8", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.17.8.tgz", "integrity": "sha512-70IJMktChk6mFTqegBYZk5MR0vquX+JEUG7rc4Jo75LTbe3BN4Lh6nUwaBXoLpx4v1zMID1vOsJ0Zv1M/3q3Jw==", - "dev": true, "license": "MIT" }, "node_modules/@algolia/client-abtesting": { @@ -6512,7 +6505,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", - "dev": true, "license": "Apache-2.0" }, "node_modules/html-encoding-sniffer": { @@ -9893,7 +9885,6 @@ "version": "10.25.4", "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -10652,7 +10643,6 @@ "version": "2.17.3", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, "license": "MIT", "peer": true }, diff --git a/frontend/package.json b/frontend/package.json index 5d7296461..84d543091 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,9 @@ "watch": "jest --watch" }, "dependencies": { + "@algolia/autocomplete-js": "^1.17.8", + "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", + "@algolia/autocomplete-theme-classic": "^1.17.8", "@fortawesome/fontawesome-svg-core": "^6.7.0", "@fortawesome/free-brands-svg-icons": "^6.7.0", "@fortawesome/free-regular-svg-icons": "^6.7.0", @@ -45,9 +48,6 @@ "tailwind-merge": "^2.5.4" }, "devDependencies": { - "@algolia/autocomplete-js": "^1.17.8", - "@algolia/autocomplete-plugin-query-suggestions": "^1.17.8", - "@algolia/autocomplete-theme-classic": "^1.17.8", "@eslint/js": "^9.15.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index 781746ae2..29cfc0efe 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -5,8 +5,8 @@ import { debounce } from 'lodash' import React from 'react' import { useEffect, useRef } from 'react' import { NEST_ENV } from 'utils/credentials' -import { client } from '../lib/algoliaClient' -import './Autosuggestion.css' +import { client } from 'lib/algoliaClient' +import 'components/Autosuggestion.css' interface SearchProps { onChange: (query: string) => void @@ -45,7 +45,6 @@ const Autocomplete = React.memo( container: containerRef.current, placeholder, autoFocus: true, - openOnFocus: true, insights: true, plugins: [querySuggestionsPlugin], initialState: { query: initialValue }, diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 2b7aa3127..26ad71f2a 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react' -import Autocomplete from './AutoSuggestion' +import Autocomplete from 'components/AutoSuggestion' interface SearchProps { onSearch: (query: string) => void diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx index 7b3de9736..c38c65815 100644 --- a/frontend/src/components/SearchPageLayout.tsx +++ b/frontend/src/components/SearchPageLayout.tsx @@ -40,11 +40,6 @@ const SearchPageLayout = ({ return (
- {!isLoaded && !isSearchBarReady && ( -
- -
- )}
- {!isLoaded ? ( + {!isSearchBarReady || !isLoaded ? (
) : ( -
- {totalPages === 0 &&
{empty}
} - {children} -
- )} - {totalPages > 1 && ( - + <> +
+ {totalPages === 0 &&
{empty}
} + {children} +
+ {totalPages > 1 && ( + + )} + )}
) From 5f6cd785074a96e4b99459e662c47f1150d7b281 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Fri, 3 Jan 2025 01:45:54 +0530 Subject: [PATCH 12/23] fixes --- frontend/Makefile | 2 +- frontend/jest.setup.ts | 7 +++---- frontend/src/components/Search.tsx | 17 +++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/Makefile b/frontend/Makefile index c198a6c56..36f25b921 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -18,4 +18,4 @@ shell-frontend: test-frontend: @docker build -f frontend/Dockerfile.test frontend -t nest-frontend-test - @docker run --env-file frontend/.env.example nest-frontend-test npm run test \ No newline at end of file + @docker run --env-file frontend/.env.example nest-frontend-test npm run test diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index 64071a0c4..43e49de3a 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -1,7 +1,10 @@ import '@testing-library/jest-dom' import { TextEncoder } from 'util' import dotenv from 'dotenv' +import React from 'react' dotenv.config() +global.React = React +global.TextEncoder = TextEncoder beforeEach(() => { Object.defineProperty(window, 'matchMedia', { writable: true, @@ -18,7 +21,3 @@ beforeEach(() => { }) }) jest.mock('@algolia/autocomplete-theme-classic', () => ({})) -import React from 'react' - -global.React = React -global.TextEncoder = TextEncoder diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 3a2176d4f..82b7ce34c 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' -import Autocomplete from 'components/AutoSuggestion' import TagManager from 'react-gtm-module' +import Autocomplete from 'components/AutoSuggestion' interface SearchProps { onSearch: (query: string) => void @@ -20,16 +20,17 @@ const SearchComponent: React.FC = ({ const handleSearchChange = useCallback( (query: string) => { onSearch(query) + TagManager.dataLayer({ + dataLayer: { + event: 'search', + search_term: query, + page_path: window.location.pathname, + }, + }) }, [onSearch] ) - TagManager.dataLayer({ - dataLayer: { - event: 'search', - search_term: searchQuery, - page_path: window.location.pathname, - }, - }) + return (
From 72acf5c96f8c318ee26ab60a28217142e3fb900e Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Fri, 3 Jan 2025 22:00:13 +0530 Subject: [PATCH 13/23] update var --- frontend/src/components/AutoSuggestion.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index 29cfc0efe..d947a4ebf 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -4,7 +4,7 @@ import '@algolia/autocomplete-theme-classic' import { debounce } from 'lodash' import React from 'react' import { useEffect, useRef } from 'react' -import { NEST_ENV } from 'utils/credentials' +import { ENVIRONMENT } from 'utils/credentials' import { client } from 'lib/algoliaClient' import 'components/Autosuggestion.css' @@ -35,7 +35,7 @@ const Autocomplete = React.memo( if (containerRef.current) { const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient: client, - indexName: `${NEST_ENV}_${indexName}_suggestions`, + indexName: `${ENVIRONMENT}_${indexName}_suggestions`, getSearchParams: () => ({ hitsPerPage: 7, }), From 43f0f12668e4e735c3360b94ad760c22ac4fcc73 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Wed, 8 Jan 2025 04:42:59 +0530 Subject: [PATCH 14/23] added_suggestion_index --- backend/Makefile | 1 + .../commands/add_suggestion_index.py | 120 ++++++++++++++++++ backend/apps/github/index/issue.py | 7 + backend/apps/owasp/index/chapter.py | 3 + backend/apps/owasp/index/committee.py | 2 + backend/apps/owasp/index/project.py | 3 + 6 files changed, 136 insertions(+) create mode 100644 backend/apps/common/management/commands/add_suggestion_index.py diff --git a/backend/Makefile b/backend/Makefile index 8348759a5..95515569c 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -36,6 +36,7 @@ index-data: @echo "Indexing Nest data" @CMD="poetry run python manage.py algolia_reindex" $(MAKE) exec-backend-command @CMD="poetry run python manage.py algolia_update_synonyms" $(MAKE) exec-backend-command + @CMD="poetry run python manage.py add_suggestion_index" $(MAKE) exec-backend-command load-data: @echo "Loading Nest data" diff --git a/backend/apps/common/management/commands/add_suggestion_index.py b/backend/apps/common/management/commands/add_suggestion_index.py new file mode 100644 index 000000000..0b044e39a --- /dev/null +++ b/backend/apps/common/management/commands/add_suggestion_index.py @@ -0,0 +1,120 @@ +"""A command to donload OWASP Nest index synonyms.""" + +from django.core.management.base import BaseCommand +from algoliasearch.query_suggestions.client import QuerySuggestionsClientSync +from django.conf import settings + + +class Command(BaseCommand): + help = 'Create query suggestions for Algolia indices' + + def handle(self, *args, **kwargs): + client = QuerySuggestionsClientSync( + settings.ALGOLIA_APPLICATION_ID, + settings.ALGOLIA_WRITE_API_KEY, "eu" + ) + print("Algolia client initialized") + response_issues = client.create_config( + configuration_with_index={ + "indexName": f"{settings.ENVIRONMENT.lower()}_issues_suggestions", + "sourceIndices": [ + { + "indexName": f"{settings.ENVIRONMENT.lower()}_issues", + "facets": [ + {"attribute": "idx_title"}, + {"attribute": "idx_project_name"}, + {"attribute": "idx_repository_name"}, + {"attribute": "idx_project_tags"}, + {"attribute": "idx_repository_topics"}, + ], + "generate": [ + ["idx_title"], + ["idx_project_name"], + ["idx_repository_name"], + ["idx_project_tags"], + ["idx_repository_topics"], + ], + }, + ], + "exclude": ["test"], + } + ) + self.stdout.write(self.style.SUCCESS( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_issues: {response_issues}")) + + response_chapters = client.create_config( + configuration_with_index={ + "indexName": f"{settings.ENVIRONMENT.lower()}_chapters_suggestions", + "sourceIndices": [ + { + "indexName": f"{settings.ENVIRONMENT.lower()}_chapters", + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + {"attribute": "idx_country"}, + {"attribute": "idx_region"}, + {"attribute": "idx_suggested_location"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ["idx_country"], + ["idx_region"], + ["idx_suggested_location"], + ], + }, + ], + "exclude": ["test"], + } + ) + self.stdout.write(self.style.SUCCESS( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_chapters: {response_chapters}")) + + response_projects = client.create_config( + configuration_with_index={ + "indexName": f"{settings.ENVIRONMENT.lower()}_projects_suggestions", + "sourceIndices": [ + { + "indexName": f"{settings.ENVIRONMENT.lower()}_projects", + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_repository_names"}, + {"attribute": "idx_tags"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ["idx_repository_names"], + ], + }, + ], + "exclude": ["test"], + } + ) + self.stdout.write(self.style.SUCCESS( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_projects: {response_projects}")) + + response_committees = client.create_config( + configuration_with_index={ + "indexName": f"{settings.ENVIRONMENT.lower()}_committees_suggestions", + "sourceIndices": [ + { + "indexName": f"{settings.ENVIRONMENT.lower()}_committees", + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ], + }, + ], + "exclude": ["test"], + } + ) + self.stdout.write(self.style.SUCCESS( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_committees: {response_committees}")) diff --git a/backend/apps/github/index/issue.py b/backend/apps/github/index/issue.py index 9ad2ebb7a..30797bd0f 100644 --- a/backend/apps/github/index/issue.py +++ b/backend/apps/github/index/issue.py @@ -39,6 +39,13 @@ class IssueIndex(AlgoliaIndex, IndexBase): ) settings = { + "attributesForFaceting": [ + "idx_title", + "idx_project_name", + "idx_repository_name", + "idx_project_tags", + "idx_repository_topics", + ], "attributeForDistinct": "idx_project_name", "minProximity": 4, "indexLanguages": ["en"], diff --git a/backend/apps/owasp/index/chapter.py b/backend/apps/owasp/index/chapter.py index 8d84b45a1..02550df6e 100644 --- a/backend/apps/owasp/index/chapter.py +++ b/backend/apps/owasp/index/chapter.py @@ -34,6 +34,9 @@ class ChapterIndex(AlgoliaIndex): settings = { "attributesForFaceting": [ "filterOnly(idx_key)", + "idx_country", + "idx_name", + "idx_tags", ], "indexLanguages": ["en"], "customRanking": [ diff --git a/backend/apps/owasp/index/committee.py b/backend/apps/owasp/index/committee.py index 2454e28e6..b1d9addc0 100644 --- a/backend/apps/owasp/index/committee.py +++ b/backend/apps/owasp/index/committee.py @@ -28,6 +28,8 @@ class CommitteeIndex(AlgoliaIndex): settings = { "attributesForFaceting": [ "filterOnly(idx_key)", + "idx_name", + "idx_tags", ], "indexLanguages": ["en"], "customRanking": [ diff --git a/backend/apps/owasp/index/project.py b/backend/apps/owasp/index/project.py index 87d95799b..a8d692183 100644 --- a/backend/apps/owasp/index/project.py +++ b/backend/apps/owasp/index/project.py @@ -41,6 +41,9 @@ class ProjectIndex(AlgoliaIndex, IndexBase): settings = { "attributesForFaceting": [ "filterOnly(idx_key)", + "idx_name", + "idx_tags", + "idx_repository_names", ], "indexLanguages": ["en"], "customRanking": [ From ffbf926f9c1d73d69c5bc6fa5f4002278d2a798e Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Wed, 8 Jan 2025 05:03:29 +0530 Subject: [PATCH 15/23] pre-commit --- .../commands/add_suggestion_index.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/backend/apps/common/management/commands/add_suggestion_index.py b/backend/apps/common/management/commands/add_suggestion_index.py index 0b044e39a..e934f7a41 100644 --- a/backend/apps/common/management/commands/add_suggestion_index.py +++ b/backend/apps/common/management/commands/add_suggestion_index.py @@ -1,17 +1,16 @@ """A command to donload OWASP Nest index synonyms.""" -from django.core.management.base import BaseCommand from algoliasearch.query_suggestions.client import QuerySuggestionsClientSync from django.conf import settings +from django.core.management.base import BaseCommand class Command(BaseCommand): - help = 'Create query suggestions for Algolia indices' + help = "Create query suggestions for Algolia indices" def handle(self, *args, **kwargs): client = QuerySuggestionsClientSync( - settings.ALGOLIA_APPLICATION_ID, - settings.ALGOLIA_WRITE_API_KEY, "eu" + settings.ALGOLIA_APPLICATION_ID, settings.ALGOLIA_WRITE_API_KEY, "eu" ) print("Algolia client initialized") response_issues = client.create_config( @@ -39,8 +38,7 @@ def handle(self, *args, **kwargs): "exclude": ["test"], } ) - self.stdout.write(self.style.SUCCESS( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_issues: {response_issues}")) + print(f"Query Suggestions for {settings.ENVIRONMENT.lower()}_issues:{response_issues}") response_chapters = client.create_config( configuration_with_index={ @@ -68,8 +66,10 @@ def handle(self, *args, **kwargs): "exclude": ["test"], } ) - self.stdout.write(self.style.SUCCESS( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_chapters: {response_chapters}")) + print( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_chapters:" + f"{response_chapters}" + ) response_projects = client.create_config( configuration_with_index={ @@ -93,8 +93,10 @@ def handle(self, *args, **kwargs): "exclude": ["test"], } ) - self.stdout.write(self.style.SUCCESS( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_projects: {response_projects}")) + print( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_projects:" + f"{response_projects}" + ) response_committees = client.create_config( configuration_with_index={ @@ -116,5 +118,8 @@ def handle(self, *args, **kwargs): "exclude": ["test"], } ) - self.stdout.write(self.style.SUCCESS( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_committees: {response_committees}")) + print( + "Query Suggestions for" + f"{settings.ENVIRONMENT.lower()}_committees: " + f"{response_committees}" + ) From ae44f85ca89b5ce4a17725039e8e718f995ecc61 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Wed, 8 Jan 2025 05:22:38 +0530 Subject: [PATCH 16/23] optimization --- backend/Makefile | 2 +- .../commands/add_suggestion_index.py | 125 ------------------ .../commands/algolia_update_suggestions.py | 90 +++++++++++++ 3 files changed, 91 insertions(+), 126 deletions(-) delete mode 100644 backend/apps/common/management/commands/add_suggestion_index.py create mode 100644 backend/apps/common/management/commands/algolia_update_suggestions.py diff --git a/backend/Makefile b/backend/Makefile index 95515569c..0d7abc2b0 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -36,7 +36,7 @@ index-data: @echo "Indexing Nest data" @CMD="poetry run python manage.py algolia_reindex" $(MAKE) exec-backend-command @CMD="poetry run python manage.py algolia_update_synonyms" $(MAKE) exec-backend-command - @CMD="poetry run python manage.py add_suggestion_index" $(MAKE) exec-backend-command + @CMD="poetry run python manage.py algolia_update_suggestions" $(MAKE) exec-backend-command load-data: @echo "Loading Nest data" diff --git a/backend/apps/common/management/commands/add_suggestion_index.py b/backend/apps/common/management/commands/add_suggestion_index.py deleted file mode 100644 index e934f7a41..000000000 --- a/backend/apps/common/management/commands/add_suggestion_index.py +++ /dev/null @@ -1,125 +0,0 @@ -"""A command to donload OWASP Nest index synonyms.""" - -from algoliasearch.query_suggestions.client import QuerySuggestionsClientSync -from django.conf import settings -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = "Create query suggestions for Algolia indices" - - def handle(self, *args, **kwargs): - client = QuerySuggestionsClientSync( - settings.ALGOLIA_APPLICATION_ID, settings.ALGOLIA_WRITE_API_KEY, "eu" - ) - print("Algolia client initialized") - response_issues = client.create_config( - configuration_with_index={ - "indexName": f"{settings.ENVIRONMENT.lower()}_issues_suggestions", - "sourceIndices": [ - { - "indexName": f"{settings.ENVIRONMENT.lower()}_issues", - "facets": [ - {"attribute": "idx_title"}, - {"attribute": "idx_project_name"}, - {"attribute": "idx_repository_name"}, - {"attribute": "idx_project_tags"}, - {"attribute": "idx_repository_topics"}, - ], - "generate": [ - ["idx_title"], - ["idx_project_name"], - ["idx_repository_name"], - ["idx_project_tags"], - ["idx_repository_topics"], - ], - }, - ], - "exclude": ["test"], - } - ) - print(f"Query Suggestions for {settings.ENVIRONMENT.lower()}_issues:{response_issues}") - - response_chapters = client.create_config( - configuration_with_index={ - "indexName": f"{settings.ENVIRONMENT.lower()}_chapters_suggestions", - "sourceIndices": [ - { - "indexName": f"{settings.ENVIRONMENT.lower()}_chapters", - "facets": [ - {"attribute": "idx_key"}, - {"attribute": "idx_name"}, - {"attribute": "idx_tags"}, - {"attribute": "idx_country"}, - {"attribute": "idx_region"}, - {"attribute": "idx_suggested_location"}, - ], - "generate": [ - ["idx_name"], - ["idx_tags"], - ["idx_country"], - ["idx_region"], - ["idx_suggested_location"], - ], - }, - ], - "exclude": ["test"], - } - ) - print( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_chapters:" - f"{response_chapters}" - ) - - response_projects = client.create_config( - configuration_with_index={ - "indexName": f"{settings.ENVIRONMENT.lower()}_projects_suggestions", - "sourceIndices": [ - { - "indexName": f"{settings.ENVIRONMENT.lower()}_projects", - "facets": [ - {"attribute": "idx_key"}, - {"attribute": "idx_name"}, - {"attribute": "idx_repository_names"}, - {"attribute": "idx_tags"}, - ], - "generate": [ - ["idx_name"], - ["idx_tags"], - ["idx_repository_names"], - ], - }, - ], - "exclude": ["test"], - } - ) - print( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_projects:" - f"{response_projects}" - ) - - response_committees = client.create_config( - configuration_with_index={ - "indexName": f"{settings.ENVIRONMENT.lower()}_committees_suggestions", - "sourceIndices": [ - { - "indexName": f"{settings.ENVIRONMENT.lower()}_committees", - "facets": [ - {"attribute": "idx_key"}, - {"attribute": "idx_name"}, - {"attribute": "idx_tags"}, - ], - "generate": [ - ["idx_name"], - ["idx_tags"], - ], - }, - ], - "exclude": ["test"], - } - ) - print( - "Query Suggestions for" - f"{settings.ENVIRONMENT.lower()}_committees: " - f"{response_committees}" - ) diff --git a/backend/apps/common/management/commands/algolia_update_suggestions.py b/backend/apps/common/management/commands/algolia_update_suggestions.py new file mode 100644 index 000000000..bc92b23b7 --- /dev/null +++ b/backend/apps/common/management/commands/algolia_update_suggestions.py @@ -0,0 +1,90 @@ +from algoliasearch.query_suggestions.client import QuerySuggestionsClientSync +from django.conf import settings +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Create query suggestions for Algolia indices" + + def handle(self, *args, **kwargs): + client = QuerySuggestionsClientSync( + settings.ALGOLIA_APPLICATION_ID, settings.ALGOLIA_WRITE_API_KEY, "eu" + ) + print("Algolia client initialized") + + entity_configs = { + "issues": { + "facets": [ + {"attribute": "idx_title"}, + {"attribute": "idx_project_name"}, + {"attribute": "idx_repository_name"}, + {"attribute": "idx_project_tags"}, + {"attribute": "idx_repository_topics"}, + ], + "generate": [ + ["idx_title"], + ["idx_project_name"], + ["idx_repository_name"], + ["idx_project_tags"], + ["idx_repository_topics"], + ], + }, + "chapters": { + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + {"attribute": "idx_country"}, + {"attribute": "idx_region"}, + {"attribute": "idx_suggested_location"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ["idx_country"], + ["idx_region"], + ["idx_suggested_location"], + ], + }, + "projects": { + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_repository_names"}, + {"attribute": "idx_tags"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ["idx_repository_names"], + ], + }, + "committees": { + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + ], + "generate": [ + ["idx_name"], + ["idx_tags"], + ], + }, + } + + # Iterate over entity + for entity, config in entity_configs.items(): + response = client.create_config( + configuration_with_index={ + "indexName": f"{settings.ENVIRONMENT.lower()}_{entity}_suggestions", + "sourceIndices": [ + { + "indexName": f"{settings.ENVIRONMENT.lower()}_{entity}", + **config, + } + ], + "exclude": ["test"], + } + ) + print( + f"Query Suggestions for {settings.ENVIRONMENT.lower()}_{entity}: {response}") From 70d285cc749eacb0722fb050d72a81aa76680d6d Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Wed, 8 Jan 2025 05:27:26 +0530 Subject: [PATCH 17/23] pre-commit --- .../common/management/commands/algolia_update_suggestions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/apps/common/management/commands/algolia_update_suggestions.py b/backend/apps/common/management/commands/algolia_update_suggestions.py index bc92b23b7..0b24fd1c3 100644 --- a/backend/apps/common/management/commands/algolia_update_suggestions.py +++ b/backend/apps/common/management/commands/algolia_update_suggestions.py @@ -1,3 +1,5 @@ +"""A command to update OWASP Nest suggestions index.""" + from algoliasearch.query_suggestions.client import QuerySuggestionsClientSync from django.conf import settings from django.core.management.base import BaseCommand @@ -86,5 +88,4 @@ def handle(self, *args, **kwargs): "exclude": ["test"], } ) - print( - f"Query Suggestions for {settings.ENVIRONMENT.lower()}_{entity}: {response}") + print(f"Query Suggestions for {settings.ENVIRONMENT.lower()}_{entity}: {response}") From a9987a7697be0b5149e0b7fd9233d0fd061b32e6 Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Tue, 7 Jan 2025 17:34:50 -0800 Subject: [PATCH 18/23] Update code --- .github/workflows/ci-cd.yaml | 1 + CONTRIBUTING.md | 1 + backend/.env.example | 1 + .../commands/algolia_update_suggestions.py | 75 ++++++++++--------- backend/settings/base.py | 1 + frontend/src/components/AutoSuggestion.tsx | 2 +- 6 files changed, 44 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml index 2c72f2f69..79143e9d1 100644 --- a/.github/workflows/ci-cd.yaml +++ b/.github/workflows/ci-cd.yaml @@ -242,6 +242,7 @@ jobs: run: | # Backend echo "DJANGO_ALGOLIA_APPLICATION_ID=${{ secrets.DJANGO_ALGOLIA_APPLICATION_ID }}" > .env.backend + echo "DJANGO_ALGOLIA_APPLICATION_REGION=${{ secrets.DJANGO_ALGOLIA_APPLICATION_REGION }}" > .env.backend echo "DJANGO_ALGOLIA_WRITE_API_KEY=${{ secrets.DJANGO_ALGOLIA_WRITE_API_KEY }}" >> .env.backend echo "DJANGO_ALLOWED_HOSTS=${{ secrets.DJANGO_ALLOWED_HOSTS }}" >> .env.backend echo "DJANGO_AWS_ACCESS_KEY_ID=${{ secrets.DJANGO_AWS_ACCESS_KEY_ID }}" >> .env.backend diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b8b0507d..ddffa0c53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,7 @@ Follow these steps to set up the OWASP Nest application: ```plaintext DJANGO_ALGOLIA_APPLICATION_ID= DJANGO_ALGOLIA_WRITE_API_KEY= + DJANGO_ALGOLIA_APPLICATION_REGION= // eu or us ``` - Update your `frontend/.env` file with the following keys from your Algolia app (use **search** API key for frontend): diff --git a/backend/.env.example b/backend/.env.example index e816226c3..6696e678d 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,5 @@ DJANGO_ALGOLIA_APPLICATION_ID=None +DJANGO_ALGOLIA_APPLICATION_REGION=None DJANGO_ALGOLIA_WRITE_API_KEY=None DJANGO_ALLOWED_HOSTS=* DJANGO_AWS_ACCESS_KEY_ID=None diff --git a/backend/apps/common/management/commands/algolia_update_suggestions.py b/backend/apps/common/management/commands/algolia_update_suggestions.py index 0b24fd1c3..63f34cbac 100644 --- a/backend/apps/common/management/commands/algolia_update_suggestions.py +++ b/backend/apps/common/management/commands/algolia_update_suggestions.py @@ -10,27 +10,12 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): client = QuerySuggestionsClientSync( - settings.ALGOLIA_APPLICATION_ID, settings.ALGOLIA_WRITE_API_KEY, "eu" + settings.ALGOLIA_APPLICATION_ID, + settings.ALGOLIA_WRITE_API_KEY, + settings.ALGOLIA_APPLICATION_REGION, ) - print("Algolia client initialized") entity_configs = { - "issues": { - "facets": [ - {"attribute": "idx_title"}, - {"attribute": "idx_project_name"}, - {"attribute": "idx_repository_name"}, - {"attribute": "idx_project_tags"}, - {"attribute": "idx_repository_topics"}, - ], - "generate": [ - ["idx_title"], - ["idx_project_name"], - ["idx_repository_name"], - ["idx_project_tags"], - ["idx_repository_topics"], - ], - }, "chapters": { "facets": [ {"attribute": "idx_key"}, @@ -48,44 +33,62 @@ def handle(self, *args, **kwargs): ["idx_suggested_location"], ], }, - "projects": { + "committees": { "facets": [ {"attribute": "idx_key"}, {"attribute": "idx_name"}, - {"attribute": "idx_repository_names"}, {"attribute": "idx_tags"}, ], "generate": [ ["idx_name"], ["idx_tags"], - ["idx_repository_names"], ], }, - "committees": { + "issues": { + "facets": [ + {"attribute": "idx_title"}, + {"attribute": "idx_project_name"}, + {"attribute": "idx_repository_name"}, + {"attribute": "idx_project_tags"}, + {"attribute": "idx_repository_topics"}, + ], + "generate": [ + ["idx_title"], + ["idx_project_name"], + ["idx_repository_name"], + ["idx_project_tags"], + ["idx_repository_topics"], + ], + }, + "projects": { "facets": [ {"attribute": "idx_key"}, {"attribute": "idx_name"}, + {"attribute": "idx_repository_names"}, {"attribute": "idx_tags"}, ], "generate": [ ["idx_name"], ["idx_tags"], + ["idx_repository_names"], ], }, } - # Iterate over entity - for entity, config in entity_configs.items(): - response = client.create_config( - configuration_with_index={ - "indexName": f"{settings.ENVIRONMENT.lower()}_{entity}_suggestions", - "sourceIndices": [ - { - "indexName": f"{settings.ENVIRONMENT.lower()}_{entity}", - **config, - } - ], - "exclude": ["test"], - } + for entity, suggestion_settings in entity_configs.items(): + source_index_name = f"{settings.ENVIRONMENT.lower()}_{entity}" + suggestions_index_name = f"{settings.ENVIRONMENT.lower()}_{entity}_suggestions" + + configuration = { + "sourceIndices": [ + { + "indexName": source_index_name, + **suggestion_settings, + } + ] + } + client.update_config( + index_name=suggestions_index_name, + configuration=configuration, ) - print(f"Query Suggestions for {settings.ENVIRONMENT.lower()}_{entity}: {response}") + print(f"Updated query suggestions index for {entity.capitalize()}") diff --git a/backend/settings/base.py b/backend/settings/base.py index eba13a2bd..6482c74fb 100644 --- a/backend/settings/base.py +++ b/backend/settings/base.py @@ -91,6 +91,7 @@ class Base(Configuration): WSGI_APPLICATION = "wsgi.application" ALGOLIA_APPLICATION_ID = values.SecretValue(environ_name="ALGOLIA_APPLICATION_ID") + ALGOLIA_APPLICATION_REGION = values.SecretValue(environ_name="ALGOLIA_APPLICATION_REGION") ALGOLIA_WRITE_API_KEY = values.SecretValue(environ_name="ALGOLIA_WRITE_API_KEY") ALGOLIA = { diff --git a/frontend/src/components/AutoSuggestion.tsx b/frontend/src/components/AutoSuggestion.tsx index d947a4ebf..0f894535c 100644 --- a/frontend/src/components/AutoSuggestion.tsx +++ b/frontend/src/components/AutoSuggestion.tsx @@ -23,7 +23,7 @@ const Autocomplete = React.memo( const debouncedOnChange = debounce((query: string) => { onChange(query) - }, 1000) + }, 750) useEffect(() => { return () => { From cd7ae7f687616e24fae2e282531f131ff783859a Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Tue, 7 Jan 2025 17:36:00 -0800 Subject: [PATCH 19/23] Update code --- .github/workflows/ci-cd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml index 79143e9d1..e30c1a7d7 100644 --- a/.github/workflows/ci-cd.yaml +++ b/.github/workflows/ci-cd.yaml @@ -242,7 +242,7 @@ jobs: run: | # Backend echo "DJANGO_ALGOLIA_APPLICATION_ID=${{ secrets.DJANGO_ALGOLIA_APPLICATION_ID }}" > .env.backend - echo "DJANGO_ALGOLIA_APPLICATION_REGION=${{ secrets.DJANGO_ALGOLIA_APPLICATION_REGION }}" > .env.backend + echo "DJANGO_ALGOLIA_APPLICATION_REGION=${{ secrets.DJANGO_ALGOLIA_APPLICATION_REGION }}" >> .env.backend echo "DJANGO_ALGOLIA_WRITE_API_KEY=${{ secrets.DJANGO_ALGOLIA_WRITE_API_KEY }}" >> .env.backend echo "DJANGO_ALLOWED_HOSTS=${{ secrets.DJANGO_ALLOWED_HOSTS }}" >> .env.backend echo "DJANGO_AWS_ACCESS_KEY_ID=${{ secrets.DJANGO_AWS_ACCESS_KEY_ID }}" >> .env.backend From 249d024449b406f01bc2759b9a90a8d51df40f50 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Thu, 9 Jan 2025 01:20:47 +0530 Subject: [PATCH 20/23] added test case for algolia_update_suggestion --- .../commands/algolia_update_synonyms_tests.py | 126 ++++++++++++++---- 1 file changed, 103 insertions(+), 23 deletions(-) diff --git a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py index 2538018af..a42d58245 100644 --- a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py +++ b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py @@ -2,30 +2,110 @@ import pytest -from apps.common.management.commands.algolia_update_synonyms import Command - - -@pytest.mark.parametrize( - "indexes", - [["IssueIndex", "ProjectIndex"]], -) -class TestAlgoliaUpdateSynonyms: - @patch("builtins.print") - @patch("apps.common.management.commands.algolia_update_synonyms.ProjectIndex") - @patch("apps.common.management.commands.algolia_update_synonyms.IssueIndex") - def test_handle(self, mock_issue_index, mock_project_index, mock_print, indexes): - mock_indexes = { - "IssueIndex": mock_issue_index, - "ProjectIndex": mock_project_index, - } - for index_name, index_instance in mock_indexes.items(): - index_instance.update_synonyms = MagicMock() - index_instance.index_name = index_name +from apps.common.management.commands.algolia_update_suggestions import Command +EXPECTED_CALL_COUNT = 4 + + +class TestUpdateSuggestionsCommand: + @pytest.mark.parametrize( + ("entity", "facets", "generate"), + [ + ( + "chapters", + [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + {"attribute": "idx_country"}, + {"attribute": "idx_region"}, + {"attribute": "idx_suggested_location"}, + ], + [ + ["idx_name"], + ["idx_tags"], + ["idx_country"], + ["idx_region"], + ["idx_suggested_location"], + ], + ), + ( + "committees", + [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_tags"}, + ], + [ + ["idx_name"], + ["idx_tags"], + ], + ), + ( + "issues", + [ + {"attribute": "idx_title"}, + {"attribute": "idx_project_name"}, + {"attribute": "idx_repository_name"}, + {"attribute": "idx_project_tags"}, + {"attribute": "idx_repository_topics"}, + ], + [ + ["idx_title"], + ["idx_project_name"], + ["idx_repository_name"], + ["idx_project_tags"], + ["idx_repository_topics"], + ], + ), + ( + "projects", + [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_repository_names"}, + {"attribute": "idx_tags"}, + ], + [ + ["idx_name"], + ["idx_tags"], + ["idx_repository_names"], + ], + ), + ], + ) + @patch("apps.common.management.commands.algolia_update_suggestions.settings") + @patch("apps.common.management.commands.algolia_update_suggestions.QuerySuggestionsClientSync") + def test_handle(self, mock_query_suggestions_client, mock_settings, entity, facets, generate): + # mocks + mock_settings.ENVIRONMENT = "testenv" + mock_settings.ALGOLIA_APPLICATION_ID = "mock_app_id" + mock_settings.ALGOLIA_WRITE_API_KEY = "mock_api_key" + mock_settings.ALGOLIA_APPLICATION_REGION = "eu" + + mock_client = MagicMock() + mock_query_suggestions_client.return_value = mock_client + + mock_client.update_config.return_value = None + + # Act command = Command() command.handle() - for index_name in indexes: - index_instance = mock_indexes[index_name] - index_instance.update_synonyms.assert_called_once() - mock_print.assert_any_call(f"Updated {index_name.capitalize()} synonyms") + mock_query_suggestions_client.assert_called_once_with("mock_app_id", "mock_api_key", "eu") + + mock_client.update_config.assert_any_call( + index_name=f"testenv_{entity}_suggestions", + configuration={ + "sourceIndices": [ + { + "indexName": f"testenv_{entity}", + "facets": facets, + "generate": generate, + } + ] + }, + ) + + # Use constant for the expected call count + assert mock_client.update_config.call_count == EXPECTED_CALL_COUNT From 287c3a89b79f337794d398a823d50ce2554d8554 Mon Sep 17 00:00:00 2001 From: Rajgupta36 Date: Thu, 9 Jan 2025 02:06:21 +0530 Subject: [PATCH 21/23] add index for users --- .../commands/algolia_update_suggestions.py | 11 ++++++++++ backend/apps/github/index/user.py | 5 +++++ .../commands/algolia_update_synonyms_tests.py | 14 +++++++++++- frontend/__tests__/src/pages/Users.test.tsx | 22 ------------------- frontend/src/pages/Users.tsx | 1 + 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/backend/apps/common/management/commands/algolia_update_suggestions.py b/backend/apps/common/management/commands/algolia_update_suggestions.py index 63f34cbac..bc226dc30 100644 --- a/backend/apps/common/management/commands/algolia_update_suggestions.py +++ b/backend/apps/common/management/commands/algolia_update_suggestions.py @@ -73,6 +73,17 @@ def handle(self, *args, **kwargs): ["idx_repository_names"], ], }, + "users": { + "facets": [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_title"}, + ], + "generate": [ + ["idx_name"], + ["idx_title"], + ], + }, } for entity, suggestion_settings in entity_configs.items(): diff --git a/backend/apps/github/index/user.py b/backend/apps/github/index/user.py index 61b7e1361..623921ca9 100644 --- a/backend/apps/github/index/user.py +++ b/backend/apps/github/index/user.py @@ -32,6 +32,11 @@ class UserIndex(AlgoliaIndex, IndexBase): ) settings = { + "attributesForFaceting": [ + "idx_key", + "idx_name", + "idx_title", + ], "attributeForDistinct": "idx_login", "minProximity": 4, "customRanking": [ diff --git a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py index a42d58245..3f57f8167 100644 --- a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py +++ b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py @@ -4,7 +4,7 @@ from apps.common.management.commands.algolia_update_suggestions import Command -EXPECTED_CALL_COUNT = 4 +EXPECTED_CALL_COUNT = 5 class TestUpdateSuggestionsCommand: @@ -72,6 +72,18 @@ class TestUpdateSuggestionsCommand: ["idx_repository_names"], ], ), + ( + "users", + [ + {"attribute": "idx_key"}, + {"attribute": "idx_name"}, + {"attribute": "idx_title"}, + ], + [ + ["idx_name"], + ["idx_title"], + ], + ), ], ) @patch("apps.common.management.commands.algolia_update_suggestions.settings") diff --git a/frontend/__tests__/src/pages/Users.test.tsx b/frontend/__tests__/src/pages/Users.test.tsx index 798168ff2..306a549fe 100644 --- a/frontend/__tests__/src/pages/Users.test.tsx +++ b/frontend/__tests__/src/pages/Users.test.tsx @@ -55,14 +55,12 @@ describe('UsersPage Component', () => { const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { expect(loadingSpinner.length).toBeGreaterThan(0) - expect(screen.queryByPlaceholderText('Search for OWASP users...')).not.toBeInTheDocument() expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) // Check loaded state await waitFor(() => { expect(screen.getByPlaceholderText('Search for OWASP users...')).toBeInTheDocument() - expect(screen.getByPlaceholderText('Search for OWASP users...')).toHaveFocus() expect(screen.getByText('John Doe')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) @@ -127,24 +125,4 @@ describe('UsersPage Component', () => { expect(navigateMock).toHaveBeenCalledWith('/community/users/user_1') }) - - test('handles search input correctly', async () => { - render() - - await waitFor(() => { - const searchInput = screen.getByPlaceholderText('Search for OWASP users...') - fireEvent.change(searchInput, { target: { value: 'John' } }) - }) - - // Wait for the API call - await waitFor(() => { - // First call is the initial page load, second call is the search - expect(fetchAlgoliaData).toHaveBeenCalledTimes(2) - // Check the latest call arguments - const lastCall = (fetchAlgoliaData as jest.Mock).mock.calls[ - (fetchAlgoliaData as jest.Mock).mock.calls.length - 1 - ] - expect(lastCall).toEqual(['users', 'John', 1]) - }) - }) }) diff --git a/frontend/src/pages/Users.tsx b/frontend/src/pages/Users.tsx index 21541dfca..bd008b329 100644 --- a/frontend/src/pages/Users.tsx +++ b/frontend/src/pages/Users.tsx @@ -44,6 +44,7 @@ const UsersPage = () => { return ( Date: Thu, 9 Jan 2025 03:04:18 +0530 Subject: [PATCH 22/23] fixes --- .../management/commands/algolia_update_synonyms_tests.py | 6 +++--- frontend/__tests__/src/pages/Chapters.test.tsx | 2 +- frontend/src/components/Search.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py index 3f57f8167..ebe935bea 100644 --- a/backend/tests/common/management/commands/algolia_update_synonyms_tests.py +++ b/backend/tests/common/management/commands/algolia_update_synonyms_tests.py @@ -4,8 +4,6 @@ from apps.common.management.commands.algolia_update_suggestions import Command -EXPECTED_CALL_COUNT = 5 - class TestUpdateSuggestionsCommand: @pytest.mark.parametrize( @@ -100,7 +98,9 @@ def test_handle(self, mock_query_suggestions_client, mock_settings, entity, face mock_client.update_config.return_value = None + expected_call_count = 5 # Act + command = Command() command.handle() @@ -120,4 +120,4 @@ def test_handle(self, mock_query_suggestions_client, mock_settings, entity, face ) # Use constant for the expected call count - assert mock_client.update_config.call_count == EXPECTED_CALL_COUNT + assert mock_client.update_config.call_count == expected_call_count diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx index 41c9243b6..8f9e01bca 100644 --- a/frontend/__tests__/src/pages/Chapters.test.tsx +++ b/frontend/__tests__/src/pages/Chapters.test.tsx @@ -80,7 +80,7 @@ describe('ChaptersPage Component', () => { }) }) - test('renders data, and pagination component concurrently after data is loaded', async () => { + test('renders SearchBar, data and pagination component concurrently after data is loaded', async () => { window.scrollTo = jest.fn() ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({ hits: mockChapterData.chapters, diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 82b7ce34c..bb2562b39 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -14,7 +14,7 @@ const SearchComponent: React.FC = ({ onSearch, placeholder, initialValue = '', - indexName = 'issue_suggestions', + indexName, onReady, }) => { const handleSearchChange = useCallback( From f3eb782a43fd65575fa3f15f10491b487362ebd8 Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets <2201626+arkid15r@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:54:08 -0800 Subject: [PATCH 23/23] Update frontend/__tests__/src/pages/Chapters.test.tsx --- frontend/__tests__/src/pages/Chapters.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx index 8f9e01bca..47ab50e9c 100644 --- a/frontend/__tests__/src/pages/Chapters.test.tsx +++ b/frontend/__tests__/src/pages/Chapters.test.tsx @@ -80,7 +80,7 @@ describe('ChaptersPage Component', () => { }) }) - test('renders SearchBar, data and pagination component concurrently after data is loaded', async () => { + test('renders SearchBar, data, and pagination component concurrently after data is loaded', async () => { window.scrollTo = jest.fn() ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({ hits: mockChapterData.chapters,