diff --git a/webui/react/package-lock.json b/webui/react/package-lock.json index b8921917f7d..6634f4cd347 100644 --- a/webui/react/package-lock.json +++ b/webui/react/package-lock.json @@ -18,7 +18,7 @@ "fp-ts": "^2.16.5", "fuse.js": "^7.0.0", "hermes-parallel-coordinates": "^0.6.17", - "hew": "npm:@hpe.com/hew@^0.6.48", + "hew": "github:determined-ai/hew#gt/logviewer-search", "humanize-duration": "^3.28.0", "immutable": "^4.3.0", "io-ts": "^2.2.21", @@ -7233,9 +7233,9 @@ } }, "node_modules/fp-ts": { - "version": "2.16.5", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.5.tgz", - "integrity": "sha512-N8T8PwMSeTKKtkm9lkj/zSTAnPC/aJIIrQhnHxxkL0KLsRCNUPANksJOlMXxcKKCo7H1ORP3No9EMD+fP0tsdA==" + "version": "2.16.9", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz", + "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==" }, "node_modules/fresh": { "version": "0.5.2", @@ -7797,8 +7797,7 @@ "node_modules/hew": { "name": "@hpe.com/hew", "version": "0.6.48", - "resolved": "https://registry.npmjs.org/@hpe.com/hew/-/hew-0.6.48.tgz", - "integrity": "sha512-hQ2ew61Ty2S1k6uYfQmiD1Od+mv4dP96St10L5kjUP2KkFTFl2EROh29lPyft6TZMnGmDGlJOeLQnQJzPaCxMA==", + "resolved": "https://github.com/determined-ai/hew.git#fa14e20f0f56622f09734ab6e7c143095b93a10b", "dependencies": { "@ant-design/icons": "^5.0.1", "@codemirror/lang-json": "^6.0.1", @@ -7811,6 +7810,7 @@ "antd": "^5.1.7", "dayjs": "^1.11.10", "debug": "^4.3.4", + "fp-ts": "^2.16.9", "io-ts": "^2.2.20", "lodash": "^4.17.21", "markdown-to-jsx": "^7.3.2", diff --git a/webui/react/package.json b/webui/react/package.json index 888e6dd2800..2f06955eff3 100644 --- a/webui/react/package.json +++ b/webui/react/package.json @@ -42,7 +42,7 @@ "fp-ts": "^2.16.5", "fuse.js": "^7.0.0", "hermes-parallel-coordinates": "^0.6.17", - "hew": "npm:@hpe.com/hew@^0.6.48", + "hew": "github:determined-ai/hew#gt/logviewer-search", "humanize-duration": "^3.28.0", "immutable": "^4.3.0", "io-ts": "^2.2.21", diff --git a/webui/react/src/pages/TrialDetails/LogViewer.tsx b/webui/react/src/pages/TrialDetails/LogViewer.tsx index ab3486ddfad..2e36dc88477 100644 --- a/webui/react/src/pages/TrialDetails/LogViewer.tsx +++ b/webui/react/src/pages/TrialDetails/LogViewer.tsx @@ -314,15 +314,6 @@ function LogViewer({ // Slight delay on scrolling to the end for the log viewer to render and resolve everything. setTimeout(() => { - // if(selectedLog) { - // virtuosoRef.current?.scrollToIndex({ - // index: 0, - // }) - // } else { - // virtuosoRef.current?.scrollToIndex({ - // index: fetchDirection === FetchDirection.Older ? 'LAST' : 0, - // }); - // } local.current.isScrollReady = true; }, 200); }, diff --git a/webui/react/src/pages/TrialDetails/LogViewerSelect.settings.ts b/webui/react/src/pages/TrialDetails/LogViewerSelect.settings.ts deleted file mode 100644 index f1eaa4503d9..00000000000 --- a/webui/react/src/pages/TrialDetails/LogViewerSelect.settings.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { LogLevelFromApi, SettingsConfig } from 'hew/internal/types'; -import { array, boolean, literal, number, string, undefined as undefinedType, union } from 'io-ts'; - -export interface Settings { - agentId?: string[]; - allocationId?: string[]; - containerId?: string[]; - level?: LogLevelFromApi[]; - rankId?: number[]; - searchText?: string; - enableRegex?: boolean; -} - -export const settingsConfigForTask = (taskId: string): SettingsConfig => - settingsConfigForLogs(taskId); - -export const settingsConfigForTrial = (id: number): SettingsConfig => - settingsConfigForLogs(id); - -const settingsConfigForLogs = (id: number | string): SettingsConfig => ({ - settings: { - agentId: { - defaultValue: undefined, - storageKey: 'agentId', - type: union([undefinedType, array(string)]), - }, - allocationId: { - defaultValue: undefined, - storageKey: 'allocationId', - type: union([undefinedType, array(string)]), - }, - containerId: { - defaultValue: undefined, - storageKey: 'containerId', - type: union([undefinedType, array(string)]), - }, - enableRegex: { - defaultValue: undefined, - storageKey: 'enableRegex', - type: union([undefinedType, boolean]), - }, - level: { - defaultValue: undefined, - storageKey: 'level', - type: union([ - undefinedType, - array( - union([ - literal(LogLevelFromApi.Critical), - literal(LogLevelFromApi.Debug), - literal(LogLevelFromApi.Error), - literal(LogLevelFromApi.Info), - literal(LogLevelFromApi.Trace), - literal(LogLevelFromApi.Unspecified), - literal(LogLevelFromApi.Warning), - ]), - ), - ]), - }, - rankId: { - defaultValue: undefined, - storageKey: 'rankId', - type: union([undefinedType, array(number)]), - }, - searchText: { - defaultValue: undefined, - storageKey: 'searchText', - type: union([undefinedType, string]), - }, - }, - storagePath: `log-viewer-filters-${id}`, -}); diff --git a/webui/react/src/pages/TrialDetails/LogViewerSelect.tsx b/webui/react/src/pages/TrialDetails/LogViewerSelect.tsx deleted file mode 100644 index 650c7784b00..00000000000 --- a/webui/react/src/pages/TrialDetails/LogViewerSelect.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import Button from 'hew/Button'; -import Icon from 'hew/Icon'; -import Input from 'hew/Input'; -import { alphaNumericSorter } from 'hew/internal/functions'; -import { LogLevelFromApi } from 'hew/internal/types'; -import Row from 'hew/Row'; -import Select, { Option, SelectValue } from 'hew/Select'; -import { isArray } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { throttle } from 'throttle-debounce'; - -interface Props { - onChange?: (filters: Filters) => void; - onReset?: () => void; - options: Filters; - showSearch: boolean; - values: Filters; - onClickSearch?: () => void; - searchOn?: boolean; -} - -export interface Filters { - agentIds?: string[]; - allocationIds?: string[]; - containerIds?: string[]; - levels?: LogLevelFromApi[]; - rankIds?: number[]; - searchText?: string; - enableRegex?: boolean; - // sources?: string[], - // stdtypes?: string[], -} - -export const ARIA_LABEL_RESET = 'Reset'; - -export const LABELS: Record = { - agentIds: 'Agents', - allocationIds: 'Allocations', - containerIds: 'Containers', - enableRegex: 'Regex', - levels: 'Levels', - rankIds: 'Ranks', - searchText: 'Searches', -}; - -const LogViewerSelect: React.FC = ({ - onChange, - onReset, - options, - showSearch, - onClickSearch, - searchOn, - values, -}: Props) => { - const [filters, setFilters] = useState(values); - - const selectOptions = useMemo(() => { - const { agentIds, allocationIds, containerIds, rankIds } = options; - return { - ...options, - agentIds: agentIds?.sort(alphaNumericSorter), - allocationIds: allocationIds?.sort(alphaNumericSorter), - containerIds: containerIds?.sort(alphaNumericSorter), - levels: Object.entries(LogLevelFromApi) - .filter((entry) => entry[1] !== LogLevelFromApi.Unspecified) - .map(([key, value]) => ({ label: key, value })), - rankIds: rankIds ? [-1].concat(rankIds).sort(alphaNumericSorter) : [-1], - }; - }, [options]); - - const moreThanOne = useMemo(() => { - return Object.keys(selectOptions).reduce( - (acc, key) => { - const filterKey = key as keyof Filters; - if (filterKey === 'enableRegex') return acc; - const options = selectOptions[filterKey]; - - // !! casts `undefined` into the boolean value of `false`. - acc[filterKey] = !!(options && options.length > 1); - - return acc; - }, - {} as Record, - ); - }, [selectOptions]); - - const isResetShown = useMemo(() => { - if (values.searchText) return true; - - const keys = Object.keys(selectOptions); - for (let i = 0; i < keys.length; i++) { - const key = keys[i] as keyof Filters; - - const value = values[key]; - if (key === 'enableRegex' && value) { - return true; - } - if (value && isArray(value) && value.length !== 0) return true; - } - - return false; - }, [selectOptions, values]); - - const throttledChangeFilter = useMemo( - () => - throttle( - 500, - (f: Filters) => { - onChange?.(f); - }, - { noLeading: true }, - ), - [onChange], - ); - - useEffect(() => { - return () => { - throttledChangeFilter.cancel(); - }; - }, [throttledChangeFilter]); - - const handleChange = useCallback( - (key: keyof Filters, caster: NumberConstructor | StringConstructor) => (value: SelectValue) => { - setFilters((prev) => { - const newF = { - ...prev, - [key]: (value as Array).map((item) => caster(item)), - }; - throttledChangeFilter(newF); - return newF; - }); - }, - [throttledChangeFilter], - ); - - const handleSearch = useCallback( - (e: React.ChangeEvent) => { - setFilters((prev) => { - const newF = { ...prev, searchText: e.target.value }; - throttledChangeFilter(newF); - return newF; - }); - }, - [throttledChangeFilter], - ); - - const handleReset = useCallback(() => { - setFilters({}); - onReset?.(); - throttledChangeFilter({}); - }, [onReset, throttledChangeFilter]); - - return ( - - {showSearch && ( - - )} - {onClickSearch && ( - - )} - {moreThanOne.allocationIds && ( - - )} - {!!selectOptions?.agentIds?.length && ( - - )} - {moreThanOne.containerIds && ( - - )} - {moreThanOne.rankIds && ( - - )} - - {isResetShown && } - - ); -}; - -export default LogViewerSelect; diff --git a/webui/react/src/pages/TrialDetails/TrialDetailsLogs.tsx b/webui/react/src/pages/TrialDetails/TrialDetailsLogs.tsx index 9ff4be60374..14f16aa25f6 100644 --- a/webui/react/src/pages/TrialDetails/TrialDetailsLogs.tsx +++ b/webui/react/src/pages/TrialDetails/TrialDetailsLogs.tsx @@ -1,6 +1,8 @@ import Checkbox from 'hew/Checkbox'; import CodeSample from 'hew/CodeSample'; import Input from 'hew/Input'; +import LogViewerSelect, { Filters } from 'hew/LogViewer/LogViewerSelect'; +import { Settings, settingsConfigForTrial } from 'hew/LogViewer/LogViewerSelect.settings'; import Message from 'hew/Message'; import Spinner from 'hew/Spinner'; import SplitPane, { Pane } from 'hew/SplitPane'; @@ -23,8 +25,6 @@ import handleError, { ErrorType } from 'utils/error'; import mergeAbortControllers from 'utils/mergeAbortControllers'; import LogViewer, { FetchConfig, FetchDirection, FetchType } from './LogViewer'; -import LogViewerSelect, { Filters } from './LogViewerSelect'; -import { Settings, settingsConfigForTrial } from './LogViewerSelect.settings'; import css from './TrialDetailsLogs.module.scss'; export interface Props { @@ -261,12 +261,12 @@ const TrialDetailsLogs: React.FC = ({ experiment, trial }: Props) => { if (!content) return; if (settings.enableRegex) { try { - new RegExp(key) + new RegExp(key); } catch { - return + return; } - } - + } + const i = settings.enableRegex ? content.match(`${key}`)?.index : content.indexOf(key); if (!i || i < 0) return; const keyLen = settings.enableRegex ? content.match(`${key}`)?.[0].length || 0 : key.length;