diff --git a/ui/src/app/workflows/components/workflow-logs-viewer/json-logs-field-selector.tsx b/ui/src/app/workflows/components/workflow-logs-viewer/json-logs-field-selector.tsx
new file mode 100644
index 000000000000..0079f7663b12
--- /dev/null
+++ b/ui/src/app/workflows/components/workflow-logs-viewer/json-logs-field-selector.tsx
@@ -0,0 +1,77 @@
+import * as React from 'react';
+import {TextInput} from '../../../shared/components/text-input';
+
+export interface SelectedJsonFields {
+ values: string[];
+}
+
+export const JsonLogsFieldSelector = ({fields, onChange}: {fields: SelectedJsonFields; onChange: (v: string[]) => void}) => {
+ const [inputFields, setInputFields] = React.useState(fields);
+ const [key, setKey] = React.useState('');
+ const deleteItem = (k: string) => {
+ const index = inputFields.values.indexOf(k, 0);
+ if (index === -1) {
+ return;
+ }
+ const values = inputFields.values.filter(v => v !== k);
+ setInputFields({values});
+ onChange(values);
+ };
+ const addItem = () => {
+ if (!key || key.trim().length === 0) {
+ return;
+ }
+ const index = inputFields.values.indexOf(key, 0);
+ if (index !== -1) {
+ return;
+ }
+ const values = [...inputFields.values, key];
+ setInputFields({values});
+ setKey('');
+ onChange(values);
+ };
+
+ return (
+ <>
+ {inputFields.values.map(k => (
+
+
{k}
+
+
+
+
+ ))}
+ {
+ if (e.key === 'Enter') {
+ addItem();
+ }
+ }}>
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const extractJsonValue = (obj: any, jsonpath: string): string | null => {
+ const fields = jsonpath.split('.');
+ try {
+ let target = obj;
+ for (const field of fields) {
+ target = target[field];
+ }
+ return typeof target === 'string' ? target : JSON.stringify(target);
+ } catch (e) {
+ return null;
+ }
+};
diff --git a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
index 574596a2ab17..14579c606170 100644
--- a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
+++ b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import {useEffect, useState} from 'react';
+import {useContext, useEffect, useState} from 'react';
import {Autocomplete} from 'argo-ui';
import moment = require('moment-timezone');
@@ -8,13 +8,17 @@ import {map, publishReplay, refCount} from 'rxjs/operators';
import * as models from '../../../../models';
import {execSpec} from '../../../../models';
import {ANNOTATION_KEY_POD_NAME_VERSION} from '../../../shared/annotations';
+import {Button} from '../../../shared/components/button';
import {ErrorNotice} from '../../../shared/components/error-notice';
import {InfoIcon, WarningIcon} from '../../../shared/components/fa-icons';
import {Links} from '../../../shared/components/links';
+import {Context} from '../../../shared/context';
import {useLocalStorage} from '../../../shared/hooks/uselocalstorage';
import {getPodName, getTemplateNameFromNode} from '../../../shared/pod-name';
+import {ScopedLocalStorage} from '../../../shared/scoped-local-storage';
import {services} from '../../../shared/services';
import {FullHeightLogsViewer} from './full-height-logs-viewer';
+import {extractJsonValue, JsonLogsFieldSelector, SelectedJsonFields} from './json-logs-field-selector';
const TZ_LOCALSTORAGE_KEY = 'DEFAULT_TZ';
@@ -70,6 +74,12 @@ const parseAndTransform = (formattedString: string, timezone: string) => {
};
export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container, archived}: WorkflowLogsViewerProps) => {
+ const storage = new ScopedLocalStorage('workflow-logs-viewer');
+ const storedJsonFields = storage.getItem('jsonFields', {
+ values: []
+ } as SelectedJsonFields);
+
+ const {popup} = useContext(Context);
const [podName, setPodName] = useState(initialPodName || '');
const [selectedContainer, setContainer] = useState(container);
const [grep, setGrep] = useState('');
@@ -87,12 +97,34 @@ export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container,
useEffect(() => {
setUITimezone(timezone);
}, [timezone]);
+ const [selectedJsonFields, setSelectedJsonFields] = useState(storedJsonFields);
useEffect(() => {
setError(null);
setLoaded(false);
const source = services.workflows.getContainerLogs(workflow, podName, nodeId, selectedContainer, grep, archived).pipe(
- map(e => (!podName ? e.podName + ': ' : '') + e.content + '\n'),
+ // extract message from LogEntry
+ map(e => {
+ const values: string[] = [];
+ const content = e.content;
+ if (selectedJsonFields.values.length > 0) {
+ try {
+ const json = JSON.parse(content);
+ selectedJsonFields.values.forEach(selectedJsonField => {
+ const value = extractJsonValue(json, selectedJsonField);
+ if (value) {
+ values.push(value);
+ }
+ });
+ } catch (e) {
+ // if not json, show content directly
+ }
+ }
+ if (values.length === 0) {
+ values.push(content);
+ }
+ return `${!podName ? e.podName + ': ' : ''}${values.join(' ')}\n`;
+ }),
// this next line highlights the search term in bold with a yellow background, white text
map(x => {
if (grep !== '') {
@@ -117,7 +149,7 @@ export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container,
);
setLogsObservable(source);
return () => subscription.unsubscribe();
- }, [workflow.metadata.namespace, workflow.metadata.name, podName, selectedContainer, grep, archived, timezone]);
+ }, [workflow.metadata.namespace, workflow.metadata.name, podName, selectedContainer, grep, archived, selectedJsonFields, timezone]);
// filter allows us to introduce a short delay, before we actually change grep
const [logFilter, setLogFilter] = useState('');
@@ -169,6 +201,23 @@ export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container,
];
const [candidateContainer, setCandidateContainer] = useState(container);
const filteredTimezones = timezones.filter(tz => tz.startsWith(uiTimezone) || uiTimezone === '');
+
+ const popupJsonFieldSelector = async () => {
+ const fields = {...selectedJsonFields};
+ const updated = await popup.confirm('Select Json Fields', () => (
+ {
+ fields.values = values;
+ }}
+ />
+ ));
+ if (updated) {
+ storage.setItem('jsonFields', fields, {values: []});
+ setSelectedJsonFields(fields);
+ }
+ };
+
return (
Logs
@@ -207,6 +256,9 @@ export const WorkflowLogsViewer = ({workflow, nodeId, initialPodName, container,
/>
)}
/>
+
{' '}