feat: [code-2061]: add recursive search toggle (#2180)

* feat: [code-2075]: fix issue
* feat: [code-2061]: fixed search
* feat: [code-2061]: add support for enable recursive
* feat: [code-2061]: fix text
* feat: [code-2061]: add recursive search toggle
This commit is contained in:
Calvin Lee 2024-07-30 17:24:28 +00:00 committed by Harness
parent 0cb3c8a4cf
commit e1724bfd5e
8 changed files with 184 additions and 21 deletions

View File

@ -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

View File

@ -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

View File

@ -246,7 +246,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
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) {

View File

@ -79,7 +79,10 @@ const Search = () => {
const [selectedRepositories, setSelectedRepositories] = useState<SelectOption[]>([])
const [selectedLanguages, setSelectedLanguages] = useState<(SelectOption & { extension?: string })[]>([])
const [keywordSearchResults, setKeyowordSearchResults] = useState<KeywordSearchResponse>()
const projectId = space?.split('/')[2]
const [recursiveSearchEnabled, setRecursiveSearchEnabled] = useState(!projectId ? true : false)
const [curScopeLabel, setCurScopeLabel] = useState<SelectOption>()
//semantic
// const [loadingSearch, setLoadingSearch] = useState(false)
const [semanticSearchResult, setSemanticSearchResult] = useState<SemanticSearchResultType[]>([])
@ -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 (
<Container className={css.main}>
<Container padding="medium" border={{ bottom: true }} flex className={css.header}>
@ -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))

View File

@ -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<SetStateAction<SelectOption[]>>
selectedLanguages: SelectOption[]
setLanguages: Dispatch<SetStateAction<SelectOption[]>>
recursiveSearchEnabled: boolean
setRecursiveSearchEnabled: React.Dispatch<React.SetStateAction<boolean>>
curScopeLabel: SelectOption | undefined
setCurScopeLabel: React.Dispatch<React.SetStateAction<SelectOption | undefined>>
}
const KeywordSearchFilters: React.FC<KeywordSearchFiltersProps> = ({
@ -39,15 +50,27 @@ const KeywordSearchFilters: React.FC<KeywordSearchFiltersProps> = ({
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<RepoRepositoryOutput[]>({
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<KeywordSearchFiltersProps> = ({
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<SelectOption>(curScopeLabel ? curScopeLabel : scopeOption[0])
return (
<div className={css.filtersCtn}>
{projectId ? null : (
<>
<Container>
<Text font={{ variation: FontVariation.SMALL_SEMI }} color={Color.GREY_600} margin={{ bottom: 'xsmall' }}>
{getString('searchScope.title')}
</Text>
<DropDown
placeholder={scopeLabel.label}
className={css.dropdown}
value={scopeLabel}
items={scopeOption}
onChange={e => {
if (e.value === ScopeLevelEnum.ALL) {
setRecursiveSearchEnabled(true)
} else if (e.value === ScopeLevelEnum.CURRENT) {
setRecursiveSearchEnabled(false)
}
setScopeLabel(e)
setCurScopeLabel(e)
}}
popoverClassName={css.branchDropdown}
/>
</Container>
</>
)}
{isRepoLevelSearch ? null : (
<Container>
<Text font={{ variation: FontVariation.SMALL_SEMI }} color={Color.GREY_600} margin={{ bottom: 'xsmall' }}>
@ -84,14 +146,16 @@ const KeywordSearchFilters: React.FC<KeywordSearchFiltersProps> = ({
items={languageOptions}
/>
</Container>
<Button
variation={ButtonVariation.LINK}
text={getString('clear')}
onClick={() => {
setRepositories([])
setLanguages([])
}}
/>
<Container>
<Button
variation={ButtonVariation.LINK}
text={getString('clear')}
onClick={() => {
setRepositories([])
setLanguages([])
}}
/>
</Container>
</div>
)
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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 = '',