mirror of
https://github.com/harness/drone.git
synced 2025-05-03 21:29:42 +08:00
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:
parent
0cb3c8a4cf
commit
e1724bfd5e
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
web/src/pages/Search/Search.module.scss.d.ts
vendored
4
web/src/pages/Search/Search.module.scss.d.ts
vendored
@ -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
|
||||
|
@ -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 = '',
|
||||
|
Loading…
Reference in New Issue
Block a user