From 10130156bafff15a5335cc41af2e27f6a2982ebc Mon Sep 17 00:00:00 2001 From: Darin Date: Thu, 1 Feb 2024 00:54:03 -0800 Subject: [PATCH] update UI to search foreach stack (#134) * update UI to search foreach stack * update test * make search scope enum --- docs/feature-flags.md | 3 +- src/components/SearchField/index.tsx | 4 +-- .../TaskListingHeader/TaskListingHeader.tsx | 2 +- src/hooks/useSearchField/index.ts | 2 +- .../useSearchRequest.test.cypress.ts | 12 ++++---- src/hooks/useSearchRequest/index.tsx | 29 ++++++++++++++----- src/translations/en.ts | 9 +++--- src/utils/FEATURE.ts | 1 + 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/docs/feature-flags.md b/docs/feature-flags.md index db578344..78bb6656 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -23,7 +23,8 @@ ui_backed: | TASK_METADATA | Show metadata for each task on task view | true | | TIMELINE_MINIMAP | Show rough presentation of lines in timeline minimap | true | | ARTIFACT_TABLE | Show artifact table on task view | false | -| ARTIFACT_SEARCH | Enable search field in timeline view to filter tasks by artifact values | false | +| ARTIFACT_SEARCH | In timeline view, filter tasks by artifact values | false | +| FOREACH_VAR_SEARCH | In timeline view, filter tasks by foreach variable or values | false | | DEBUG_VIEW | Expose this view in help menu as a link | true | | CARDS | Show cards on task view | true | | HIDE_LOGO | Hide Metaflow logo | false | diff --git a/src/components/SearchField/index.tsx b/src/components/SearchField/index.tsx index 5b2174fc..678ded20 100644 --- a/src/components/SearchField/index.tsx +++ b/src/components/SearchField/index.tsx @@ -39,7 +39,7 @@ const SearchField: React.FC = ({ return ( = ({ autoCompleteEnabled={autoCompleteEnabled} tip="key:value" status={results.status} - infoMsg={t('search.artifactInfo') ?? ''} + infoMsg={t('search.filterInfo') ?? ''} errorMsg={(results.status === 'Error' && results.errorMsg) || undefined} /> diff --git a/src/components/TaskListingHeader/TaskListingHeader.tsx b/src/components/TaskListingHeader/TaskListingHeader.tsx index c81a1e94..7b986ca4 100644 --- a/src/components/TaskListingHeader/TaskListingHeader.tsx +++ b/src/components/TaskListingHeader/TaskListingHeader.tsx @@ -75,7 +75,7 @@ const TaskListingHeader: React.FC = ({ collapse={() => onToggleCollapse('collapse')} isAnyGroupOpen={isAnyGroupOpen} /> - {FEATURE_FLAGS.ARTIFACT_SEARCH && ( + {(FEATURE_FLAGS.ARTIFACT_SEARCH || FEATURE_FLAGS.FOREACH_VAR_SEARCH) && ( { it('parseSearchValue', () => { expect(parseSearchValue('')).to.equal(null); - expect(parseSearchValue('test')).to.eql({ key: 'test' }); - expect(parseSearchValue('test:')).to.eql({ key: 'test' }); - expect(parseSearchValue('test: ')).to.eql({ key: 'test' }); - expect(parseSearchValue('test : ')).to.eql({ key: 'test ' }); - expect(parseSearchValue('test :val')).to.eql({ key: 'test ', value: 'val' }); - expect(parseSearchValue('test :val ')).to.eql({ key: 'test ', value: 'val' }); + expect(parseSearchValue('test')).to.eql({ key: 'test', scope: '' }); + expect(parseSearchValue('test:')).to.eql({ key: 'test', scope: '' }); + expect(parseSearchValue('test: ')).to.eql({ key: 'test', scope: '' }); + expect(parseSearchValue('test : ')).to.eql({ key: 'test ', scope: '' }); + expect(parseSearchValue('test :val')).to.eql({ key: 'test ', value: 'val', scope: '' }); + expect(parseSearchValue('test :val ')).to.eql({ key: 'test ', value: 'val', scope: '' }); }); }); diff --git a/src/hooks/useSearchRequest/index.tsx b/src/hooks/useSearchRequest/index.tsx index d4bbcff9..5e770b71 100755 --- a/src/hooks/useSearchRequest/index.tsx +++ b/src/hooks/useSearchRequest/index.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import useWebsocketRequest, { OnOpen, OnUpdate, OnClose, OnError } from '../useWebsocketRequest'; +import FEATURE_FLAGS from '../../utils/FEATURE'; export type SearchResult = | { @@ -41,18 +42,32 @@ export interface HookConfig { enabled?: boolean; } -interface SearchKeyValuePair { +interface SearchTerm { key: string; + scope: string; value?: string; } -export const parseSearchValue = (searchValue: string): SearchKeyValuePair | null => { +enum SearchScope { + Artifact = 'ARTIFACT', + ForeachVariable = 'FOREACH_VARIABLE', +} + +export const parseSearchValue = (searchValue: string): SearchTerm | null => { + const scope: string[] = []; + if (FEATURE_FLAGS.ARTIFACT_SEARCH) { + scope.push(SearchScope.Artifact); + } + if (FEATURE_FLAGS.FOREACH_VAR_SEARCH) { + scope.push(SearchScope.ForeachVariable); + } + const components = (searchValue || '').trim().split(':').filter(Boolean); if (components.length > 0) { if (components[1]) { - return { key: components[0], value: components[1] }; + return { key: components[0], value: components[1], scope: scope.join(',') }; } - return { key: components[0] }; + return { key: components[0], scope: scope.join(',') }; } return null; }; @@ -67,14 +82,14 @@ export default function useSearchRequest({ onOpen, enabled = true, }: HookConfig): void { - const searchKv = useMemo(() => { + const searchTerm = useMemo(() => { return parseSearchValue(searchValue); }, [searchValue]); useWebsocketRequest({ url, - queryParams: searchKv !== null ? (searchKv as unknown as Record) : {}, - enabled: searchKv !== null && enabled, + queryParams: searchTerm !== null ? (searchTerm as unknown as Record) : {}, + enabled: searchTerm !== null && enabled, onConnecting, onUpdate, onClose, diff --git a/src/translations/en.ts b/src/translations/en.ts index 89228193..8d3acabb 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -159,7 +159,7 @@ const en = { 'std-out': 'stdout', 'std-err': 'stderr', artifacts: 'Artifacts', - 'search-tasks-tip': 'Search: artifact_name:value', + 'search-tasks-tip': 'Search: name:value', 'no-logs': 'No logs', 'no-preload-logs': 'No logs. Logs will be checked again momentarily', 'logs-only-available-AWS': 'Logs were not found from AWS.', @@ -201,8 +201,8 @@ const en = { search: { search: 'Search', - artifact: 'Artifact', - artifactInfo: 'You can wrap value "" to search for an exact match.', + filter: 'Filter', + filterInfo: 'You can wrap value "" to search for an exact match.', 'no-results': 'No search results', 'no-tasks': 'No tasks with selected settings', 'failed-to-search': 'Failed to search', @@ -310,7 +310,8 @@ const en = { TASK_METADATA_msg: 'Show metadata for each task on task view.', TIMELINE_MINIMAP_msg: 'Show rough presentation of lines in timeline minimap.', ARTIFACT_TABLE_msg: 'Show artifact table on task view.', - ARTIFACT_SEARCH_msg: 'Enable search field in timeline view to filter tasks by artifact values.', + ARTIFACT_SEARCH_msg: 'In timeline view, filter tasks by artifact values.', + FOREACH_VAR_SEARCH_msg: 'In timeline view, filter tasks by foreach variable or values.', DEBUG_VIEW_msg: 'Expose this view in help menu as a link.', CARDS_msg: 'Show cards on task view.', HIDE_LOGO_msg: 'Hide Metaflow logo.', diff --git a/src/utils/FEATURE.ts b/src/utils/FEATURE.ts index ee5b185c..77e4318d 100644 --- a/src/utils/FEATURE.ts +++ b/src/utils/FEATURE.ts @@ -7,6 +7,7 @@ const FEATURE_FLAGS = { TIMELINE_MINIMAP: (process.env.REACT_APP_FEATURE_TIMELINE_MINIMAP || '1') === '1', ARTIFACT_TABLE: (process.env.REACT_APP_FEATURE_ARTIFACT_TABLE || '0') === '1', ARTIFACT_SEARCH: (process.env.REACT_APP_FEATURE_ARTIFACT_SEARCH || '0') === '1', + FOREACH_VAR_SEARCH: (process.env.REACT_APP_FEATURE_FOREACH_VAR_SEARCH || '0') === '1', DEBUG_VIEW: (process.env.REACT_APP_FEATURE_DEBUG_VIEW || '1') === '1', CARDS: (process.env.REACT_APP_FEATURE_CARDS || '1') === '1',