diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 94ce27cef..cf15997b5 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -8,6 +8,7 @@ export interface StringsMap { accessControl: string accountEmail: string accountSetting: string + accounts: string active: string activeBranches: string add: string @@ -904,6 +905,8 @@ export interface StringsMap { readMe: string reader: string rebaseMerge: string + recursiveSearchLabel: string + recursiveSearchTooltip: string refresh: string reject: string rejected: string @@ -962,6 +965,13 @@ export interface StringsMap { searchExamples: string searchHeader: string searchResult: string + 'searchScope.accOnly': string + 'searchScope.allScopes': string + 'searchScope.base': string + 'searchScope.orgAndProj': string + 'searchScope.orgOnly': string + 'searchScope.placeholder': string + 'searchScope.title': string secret: string 'secrets.create': string 'secrets.createSecret': string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index f59c45257..1e36c96fb 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -1275,3 +1275,13 @@ sshCard: personalAccessToken: Personal Access Token noTokensText: There are no personal access tokens associated with this account lastUsed: Last Used +recursiveSearchTooltip: Disabling recursive search will only search at the account level and exclude organization and project repositories. +recursiveSearchLabel: 'Recursive search: show results across Repositories within {account} Organization and Projects' +accounts: "Account's" +searchScope: + base: 'Search scope: {scope}' + allScopes: Account, organizations and projects + accOnly: Account Only + orgOnly: Organizations only + orgAndProj: Organizations and Projects + title: Search scope diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx index eef12e404..e141246c9 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx @@ -246,7 +246,7 @@ export const PullRequestActionsBox: React.FC = ({ return { commitTitle: messageTitle, commitMessage: mergeOption.method === MergeStrategy.SQUASH ? messageString : '' - } + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [pullReqCommits, mergeOption, pullReqMetadata]) if (pullReqMetadata.state === PullRequestFilterOption.MERGED) { diff --git a/web/src/pages/Search/CodeSearchPage.tsx b/web/src/pages/Search/CodeSearchPage.tsx index 6294db0f7..f19075a17 100644 --- a/web/src/pages/Search/CodeSearchPage.tsx +++ b/web/src/pages/Search/CodeSearchPage.tsx @@ -79,7 +79,10 @@ const Search = () => { const [selectedRepositories, setSelectedRepositories] = useState([]) const [selectedLanguages, setSelectedLanguages] = useState<(SelectOption & { extension?: string })[]>([]) const [keywordSearchResults, setKeyowordSearchResults] = useState() + const projectId = space?.split('/')[2] + const [recursiveSearchEnabled, setRecursiveSearchEnabled] = useState(!projectId ? true : false) + const [curScopeLabel, setCurScopeLabel] = useState() //semantic // const [loadingSearch, setLoadingSearch] = useState(false) const [semanticSearchResult, setSemanticSearchResult] = useState([]) @@ -121,11 +124,14 @@ const Search = () => { query += ` case:no` } + // Clear previous results + setKeyowordSearchResults(undefined) const res = await mutate({ repo_paths: repoPath ? [repoPath] : repoPaths, space_paths: !repoPath && !repoPaths.length ? [space] : [], query, - max_result_count: maxResultCount + max_result_count: maxResultCount, + recursive: recursiveSearchEnabled }) setKeyowordSearchResults(res) @@ -136,7 +142,7 @@ const Search = () => { showError(getErrorMessage(error)) } }, 300), - [selectedLanguages, selectedRepositories, repoPath, mode] + [selectedLanguages, selectedRepositories, repoPath, mode, recursiveSearchEnabled] ) const performSemanticSearch = useCallback(() => { @@ -154,7 +160,7 @@ const Search = () => { }) .finally(() => { // setLoadingSearch(false) - }) + }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchTerm, history, location, repoPath, sendSemanticSearch, showError, mode]) useEffect(() => { @@ -162,8 +168,15 @@ const Search = () => { debouncedSearch(searchTerm) } else if (searchTerm && repoMetadata?.path && mode === SEARCH_MODE.SEMANTIC) { performSemanticSearch() - } + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedLanguages, selectedRepositories, repoMetadata?.path]) + + useEffect(() => { + setTimeout(() => { + debouncedSearch(searchTerm) + }, 0) // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setRecursiveSearchEnabled, recursiveSearchEnabled]) + return ( @@ -210,6 +223,10 @@ const Search = () => { selectedRepositories={selectedRepositories} setLanguages={setSelectedLanguages} setRepositories={setSelectedRepositories} + recursiveSearchEnabled={recursiveSearchEnabled} + setRecursiveSearchEnabled={setRecursiveSearchEnabled} + curScopeLabel={curScopeLabel} + setCurScopeLabel={setCurScopeLabel} /> )} @@ -279,6 +296,11 @@ interface CodeBlock { export const SearchResult = ({ fileMatch, searchTerm }: { fileMatch: FileMatch; searchTerm: string }) => { const { routes } = useAppContext() + const space = useGetSpaceParam() + const accId = space?.split('/')[0] + + const projectId = space?.split('/')[2] + const orgId = space?.split('/')[1] const [isCollapsed, setIsCollapsed] = useToggle(false) const [showMoreMatchs, setShowMoreMatches] = useState(false) @@ -326,8 +348,18 @@ export const SearchResult = ({ fileMatch, searchTerm }: { fileMatch: FileMatch; }, [fileMatch]) const collapsedCodeBlocks = showMoreMatchs ? codeBlocks.slice(0, 25) : codeBlocks.slice(0, 2) - const repoName = fileMatch.repo_path.split('/').pop() + const repoPathParts = fileMatch.repo_path.split('/') + let repoName = '' + if (accId && !orgId && !projectId) { + repoName = repoPathParts.slice(1).join('/') + } else if (accId && orgId && !projectId) { + repoName = repoPathParts.slice(2).join('/') + } else if (accId && orgId && projectId) { + repoName = fileMatch.repo_path.split('/').pop() as string + } else { + repoName = fileMatch.repo_path.split('/').pop() as string + } const isFileMatch = fileMatch.matches?.[0]?.line_num === 0 const flattenedMatches = flatten(codeBlocks.map(codeBlock => codeBlock.fragmentMatches)) diff --git a/web/src/pages/Search/KeywordSearchFilters.tsx b/web/src/pages/Search/KeywordSearchFilters.tsx index de73b6ad1..bf752066d 100644 --- a/web/src/pages/Search/KeywordSearchFilters.tsx +++ b/web/src/pages/Search/KeywordSearchFilters.tsx @@ -1,12 +1,19 @@ -import React, { Dispatch, SetStateAction } from 'react' -import { Container, Text, type SelectOption, MultiSelectDropDown, Button, ButtonVariation } from '@harnessio/uicore' +import React, { Dispatch, SetStateAction, useMemo, useState } from 'react' +import { + Container, + Text, + type SelectOption, + MultiSelectDropDown, + Button, + ButtonVariation, + DropDown +} from '@harnessio/uicore' import { Color, FontVariation } from '@harnessio/design-system' import { useGet } from 'restful-react' - import { useStrings } from 'framework/strings' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import type { RepoRepositoryOutput } from 'services/code' - +import { ScopeLevelEnum } from 'utils/Utils' import css from './Search.module.scss' const languageOptions = [ @@ -32,6 +39,10 @@ interface KeywordSearchFiltersProps { setRepositories: Dispatch> selectedLanguages: SelectOption[] setLanguages: Dispatch> + recursiveSearchEnabled: boolean + setRecursiveSearchEnabled: React.Dispatch> + curScopeLabel: SelectOption | undefined + setCurScopeLabel: React.Dispatch> } const KeywordSearchFilters: React.FC = ({ @@ -39,15 +50,27 @@ const KeywordSearchFilters: React.FC = ({ selectedLanguages, selectedRepositories, setLanguages, - setRepositories + setRepositories, + recursiveSearchEnabled, + setRecursiveSearchEnabled, + curScopeLabel, + setCurScopeLabel }) => { const { getString } = useStrings() const space = useGetSpaceParam() + const accId = space?.split('/')[0] + const orgId = space?.split('/')[1] + const projectId = space?.split('/')[2] + const enabledRecursive = useMemo(() => recursiveSearchEnabled, [recursiveSearchEnabled]) const { data } = useGet({ path: `/api/v1/spaces/${space}/+/repos`, debounce: 500, - lazy: isRepoLevelSearch + + lazy: isRepoLevelSearch, + queryParams: { + recursive: enabledRecursive + } }) const repositoryOptions = @@ -56,8 +79,47 @@ const KeywordSearchFilters: React.FC = ({ value: String(repository.path) })) || [] + const scopeOption = [ + accId && !orgId + ? { + label: getString('searchScope.allScopes'), + value: ScopeLevelEnum.ALL + } + : null, + accId && !orgId ? { label: getString('searchScope.accOnly'), value: ScopeLevelEnum.CURRENT } : null, + orgId ? { label: getString('searchScope.orgAndProj'), value: ScopeLevelEnum.ALL } : null, + orgId ? { label: getString('searchScope.orgOnly'), value: ScopeLevelEnum.CURRENT } : null + ].filter(Boolean) as SelectOption[] + + const [scopeLabel, setScopeLabel] = useState(curScopeLabel ? curScopeLabel : scopeOption[0]) return (
+ {projectId ? null : ( + <> + + + {getString('searchScope.title')} + + { + if (e.value === ScopeLevelEnum.ALL) { + setRecursiveSearchEnabled(true) + } else if (e.value === ScopeLevelEnum.CURRENT) { + setRecursiveSearchEnabled(false) + } + setScopeLabel(e) + setCurScopeLabel(e) + }} + popoverClassName={css.branchDropdown} + /> + + + )} + {isRepoLevelSearch ? null : ( @@ -84,14 +146,16 @@ const KeywordSearchFilters: React.FC = ({ items={languageOptions} /> -
) } diff --git a/web/src/pages/Search/Search.module.scss b/web/src/pages/Search/Search.module.scss index bd5336717..95850829a 100644 --- a/web/src/pages/Search/Search.module.scss +++ b/web/src/pages/Search/Search.module.scss @@ -261,7 +261,7 @@ .filtersCtn { align-items: flex-end; display: grid; - grid-template-columns: max-content max-content min-content; + grid-template-columns: max-content max-content min-content min-content; column-gap: var(--spacing-medium); margin-bottom: var(--spacing-large); @@ -275,6 +275,9 @@ .multiSelect { min-width: 203px; } + .dropdown { + min-width: 303px; + } } .searchResult { @@ -360,3 +363,39 @@ .aidaFeedback { padding: 5px 10px !important; } + +.searchContainer { + border-radius: 4px; + border: 1px solid var(--grey-200) !important; + padding: 3px var(--spacing-small) !important; + + :global { + .bp3-popover-wrapper { + justify-content: center !important; + display: flex !important; + } + } +} + +.icon { + > svg { + fill: var(--primary-7) !important; + + > path { + fill: var(--primary-7) !important; + } + } +} + +.dropdown { + flex-grow: 2; +} + +.branchDropdown { + min-width: 303px !important; + :global { + .bp3-input-group { + display: none !important; + } + } +} diff --git a/web/src/pages/Search/Search.module.scss.d.ts b/web/src/pages/Search/Search.module.scss.d.ts index b8c8a19a8..368ec88e0 100644 --- a/web/src/pages/Search/Search.module.scss.d.ts +++ b/web/src/pages/Search/Search.module.scss.d.ts @@ -18,6 +18,8 @@ // This is an auto-generated file export declare const aidaFeedback: string export declare const aiLabel: string +export declare const branchDropdown: string +export declare const dropdown: string export declare const editorCtn: string export declare const fileContent: string export declare const filename: string @@ -26,6 +28,7 @@ export declare const filtersCtn: string export declare const header: string export declare const highlight: string export declare const highlightLineNumber: string +export declare const icon: string export declare const isCollapsed: string export declare const layout: string export declare const loadingSpinner: string @@ -41,6 +44,7 @@ export declare const result: string export declare const resultHeader: string export declare const resultTitle: string export declare const searchBox: string +export declare const searchContainer: string export declare const searchResult: string export declare const selected: string export declare const semanticStamp: string diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index 4c3366119..75585daf0 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -638,6 +638,10 @@ export function formatBytes(bytes: number, decimals = 2) { return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` } +export enum ScopeLevelEnum { + ALL = 'all', + CURRENT = 'current' +} export enum PullRequestCheckType { EMPTY = '',