diff --git a/ui/src/app/cluster-workflow-templates/cluster-workflow-template-list.tsx b/ui/src/app/cluster-workflow-templates/cluster-workflow-template-list.tsx index 2e52d4591658..c5cffe6c2f96 100644 --- a/ui/src/app/cluster-workflow-templates/cluster-workflow-template-list.tsx +++ b/ui/src/app/cluster-workflow-templates/cluster-workflow-template-list.tsx @@ -10,7 +10,7 @@ import {ErrorNotice} from '../shared/components/error-notice'; import {ExampleManifests} from '../shared/components/example-manifests'; import {InfoIcon} from '../shared/components/fa-icons'; import {Loading} from '../shared/components/loading'; -import {Timestamp} from '../shared/components/timestamp'; +import {Timestamp, TimestampSwitch} from '../shared/components/timestamp'; import {useCollectEvent} from '../shared/use-collect-event'; import {ZeroState} from '../shared/components/zero-state'; import {Context} from '../shared/context'; @@ -20,6 +20,7 @@ import {services} from '../shared/services'; import {ClusterWorkflowTemplateCreator} from './cluster-workflow-template-creator'; import './cluster-workflow-template-list.scss'; +import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp'; export function ClusterWorkflowTemplateList({history, location}: RouteComponentProps) { const {navigation} = useContext(Context); @@ -51,6 +52,8 @@ export function ClusterWorkflowTemplateList({history, location}: RouteComponentP useCollectEvent('openedClusterWorkflowTemplateList'); + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useTimestamp(TIMESTAMP_KEYS.CLUSTER_WORKFLOW_TEMPLATE_LIST); + function renderTemplates() { if (error) { return ; @@ -75,7 +78,9 @@ export function ClusterWorkflowTemplateList({history, location}: RouteComponentP
NAME
-
CREATED
+
+ CREATED +
{templates.map(t => ( @@ -84,7 +89,7 @@ export function ClusterWorkflowTemplateList({history, location}: RouteComponentP
{t.metadata.name}
- +
))} diff --git a/ui/src/app/cron-workflows/cron-workflow-list.tsx b/ui/src/app/cron-workflows/cron-workflow-list.tsx index 48809f7d5871..cbcf08627e84 100644 --- a/ui/src/app/cron-workflows/cron-workflow-list.tsx +++ b/ui/src/app/cron-workflows/cron-workflow-list.tsx @@ -12,7 +12,7 @@ import {ErrorNotice} from '../shared/components/error-notice'; import {ExampleManifests} from '../shared/components/example-manifests'; import {InfoIcon} from '../shared/components/fa-icons'; import {Loading} from '../shared/components/loading'; -import {Timestamp} from '../shared/components/timestamp'; +import {Timestamp, TimestampSwitch} from '../shared/components/timestamp'; import {useCollectEvent} from '../shared/use-collect-event'; import {ZeroState} from '../shared/components/zero-state'; import {Context} from '../shared/context'; @@ -27,6 +27,7 @@ import {CronWorkflowFilters} from './cron-workflow-filters'; import {PrettySchedule} from './pretty-schedule'; import './cron-workflow-list.scss'; +import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp'; const learnMore = Learn more; @@ -40,6 +41,9 @@ export function CronWorkflowList({match, location, history}: RouteComponentProps const [labels, setLabels] = useState([]); const [states, setStates] = useState(['Running', 'Suspended']); // check all by default + const [storedDisplayISOFormatCreation, setStoredDisplayISOFormatCreation] = useTimestamp(TIMESTAMP_KEYS.CRON_WORKFLOW_LIST_CREATION); + const [storedDisplayISOFormatNextScheduled, setStoredDisplayISOFormatNextScheduled] = useTimestamp(TIMESTAMP_KEYS.CRON_WORKFLOW_LIST_NEXT_SCHEDULED); + useEffect( useQueryParams(history, p => { setSidePanel(p.get('sidePanel') === 'true'); @@ -135,13 +139,22 @@ export function CronWorkflowList({match, location, history}: RouteComponentProps
-
NAME
+
NAME
NAMESPACE
TimeZone
SCHEDULE
-
-
CREATED
-
NEXT RUN
+
+
+ CREATED{' '} + +
+
+ NEXT RUN{' '} + +
{cronWorkflows.map(w => (
{w.spec.suspend ? : }
-
+
{w.metadata.annotations?.[ANNOTATION_TITLE] ?? w.metadata.name} {w.metadata.annotations?.[ANNOTATION_DESCRIPTION] ?

{w.metadata.annotations[ANNOTATION_DESCRIPTION]}

: null}
-
{w.metadata.namespace}
+
{w.metadata.namespace}
{w.spec.timezone}
{w.spec.schedule != '' @@ -179,11 +192,17 @@ export function CronWorkflowList({match, location, history}: RouteComponentProps )}
-
- +
+
-
- {w.spec.suspend ? '' : {() => }} +
+ {w.spec.suspend ? ( + '' + ) : ( + + {() => } + + )}
))} diff --git a/ui/src/app/cron-workflows/cron-workflow-status-viewer.tsx b/ui/src/app/cron-workflows/cron-workflow-status-viewer.tsx index e3a72e9a6242..6a82045871d4 100644 --- a/ui/src/app/cron-workflows/cron-workflow-status-viewer.tsx +++ b/ui/src/app/cron-workflows/cron-workflow-status-viewer.tsx @@ -6,6 +6,7 @@ import {Timestamp} from '../shared/components/timestamp'; import {ConditionsPanel} from '../shared/conditions-panel'; import {WorkflowLink} from '../workflows/components/workflow-link'; import {PrettySchedule} from './pretty-schedule'; +import {TIMESTAMP_KEYS} from '../shared/use-timestamp'; export function CronWorkflowStatusViewer({spec, status}: {spec: CronWorkflowSpec; status: CronWorkflowStatus}) { if (status === null) { @@ -35,7 +36,7 @@ export function CronWorkflowStatusViewer({spec, status}: {spec: CronWorkflowSpec ) }, - {title: 'Last Scheduled Time', value: }, + {title: 'Last Scheduled Time', value: }, {title: 'Conditions', value: } ].map(attr => (
diff --git a/ui/src/app/event-sources/event-source-list.tsx b/ui/src/app/event-sources/event-source-list.tsx index a2796cfb7769..f6824cfc66a6 100644 --- a/ui/src/app/event-sources/event-source-list.tsx +++ b/ui/src/app/event-sources/event-source-list.tsx @@ -14,7 +14,7 @@ import {ErrorNotice} from '../shared/components/error-notice'; import {Node} from '../shared/components/graph/types'; import {Loading} from '../shared/components/loading'; import {NamespaceFilter} from '../shared/components/namespace-filter'; -import {Timestamp} from '../shared/components/timestamp'; +import {Timestamp, TimestampSwitch} from '../shared/components/timestamp'; import {useCollectEvent} from '../shared/use-collect-event'; import {ZeroState} from '../shared/components/zero-state'; import {Context} from '../shared/context'; @@ -26,6 +26,7 @@ import {Utils} from '../shared/utils'; import {EventsPanel} from '../workflows/components/events-panel'; import {EventSourceCreator} from './event-source-creator'; import {EventSourceLogsViewer} from './event-source-log-viewer'; +import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp'; const learnMore = Learn more; @@ -88,6 +89,8 @@ export function EventSourceList({match, location, history}: RouteComponentProps< useCollectEvent('openedEventSourceList'); + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useTimestamp(TIMESTAMP_KEYS.EVENT_SOURCE_LIST_CREATION); + return (
NAME
NAMESPACE
-
CREATED
+
+ CREATED +
LOGS
{eventSources.map(es => ( @@ -140,7 +145,7 @@ export function EventSourceList({match, location, history}: RouteComponentProps<
{es.metadata.name}
{es.metadata.namespace}
- +
Learn more; @@ -83,6 +84,8 @@ export function SensorList({match, location, history}: RouteComponentProps) const loading = !error && !sensors; const zeroState = (sensors || []).length === 0; + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useTimestamp(TIMESTAMP_KEYS.SENSOR_LIST_CREATION); + return ( )
NAME
NAMESPACE
-
CREATED
+
+ CREATED +
LOGS
{sensors.map(s => ( @@ -138,7 +143,7 @@ export function SensorList({match, location, history}: RouteComponentProps)
{s.metadata.name}
{s.metadata.namespace}
- +
-; -export function Timestamp({date}: {date: Date | string | number}) { - const tooltip = (utc: Date | string | number) => { - return utc.toString() + '\n' + new Date(utc.toString()).toLocaleString(); - }; return ( - {date === null || date === undefined ? ( - '-' - ) : ( - - {() => ago(new Date(date))} - - )} + + {displayISOFormatValue ? ( + new Date(date.toString()).toISOString() + ) : ( + <> + {displayLocalDateTime ? ( + <> + {new Date(date.toString()).toLocaleString()} ({() => ago(new Date(date))}) + + ) : ( + {() => ago(new Date(date))} + )} + + )} + + {timestampKey ? : null} ); } + +export function TimestampSwitch({storedDisplayISOFormat, setStoredDisplayISOFormat}: {storedDisplayISOFormat: boolean; setStoredDisplayISOFormat: (value: boolean) => void}) { + return ( + + + { + e.stopPropagation(); + e.preventDefault(); + setStoredDisplayISOFormat(!storedDisplayISOFormat); + }} + /> + + + ); +} diff --git a/ui/src/app/shared/use-timestamp.ts b/ui/src/app/shared/use-timestamp.ts new file mode 100644 index 000000000000..53085de8a8c0 --- /dev/null +++ b/ui/src/app/shared/use-timestamp.ts @@ -0,0 +1,39 @@ +import {useState} from 'react'; +import {ScopedLocalStorage} from './scoped-local-storage'; + +export enum TIMESTAMP_KEYS { + WORKFLOW_NODE_STARTED = 'workflowNodeStarted', + WORKFLOW_NODE_FINISHED = 'workflowNodeFinished', + CLUSTER_WORKFLOW_TEMPLATE_LIST = 'clusterWorkflowTemplateList', + CRON_WORKFLOW_LIST_CREATION = 'cronWorkflowListCreation', + CRON_WORKFLOW_LIST_NEXT_SCHEDULED = 'cronWorkflowListNextScheduled', + CRON_WORKFLOW_STATUS_LAST_SCHEDULED = 'cronWorkflowStatusLastScheduled', + EVENT_SOURCE_LIST_CREATION = 'eventSourceListCreation', + SENSOR_LIST_CREATION = 'sensorListCreation', + EVENTS_PANEL_LAST = 'eventsPanelLast', + WORKFLOW_ARTIFACTS_CREATED = 'workflowArtifactsCreated', + WORKFLOW_SUMMARY_PANEL_START = 'workflowSummaryPanelStart', + WORKFLOW_SUMMARY_PANEL_END = 'workflowSummaryPanelEnd', + WORKFLOW_NODE_ARTIFACT_CREATED = 'workflowNodeArtifactCreated', + WORKFLOW_TEMPLATE_LIST_CREATION = 'workflowTemplateListCreation', + WORKFLOWS_ROW_STARTED = 'workflowsRowStarted', + WORKFLOWS_ROW_FINISHED = 'workflowsRowFinished', + CRON_ROW_STARTED = 'cronRowStarted', + CRON_ROW_FINISHED = 'cronRowFinished' +} + +const storage = new ScopedLocalStorage('Timestamp'); + +// key is used to store the preference in local storage +const useTimestamp = (timestampKey: TIMESTAMP_KEYS): [boolean, (value: boolean) => void] => { + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useState(storage.getItem(`displayISOFormat-${timestampKey}`, false)); + + const handleStoredDisplayISOFormatChange = (value: boolean) => { + setStoredDisplayISOFormat(value); + storage.setItem(`displayISOFormat-${timestampKey}`, value, false); + }; + + return [storedDisplayISOFormat, handleStoredDisplayISOFormatChange]; +}; + +export default useTimestamp; diff --git a/ui/src/app/workflow-templates/workflow-template-list.tsx b/ui/src/app/workflow-templates/workflow-template-list.tsx index 7f40a15db439..8e11dc92c348 100644 --- a/ui/src/app/workflow-templates/workflow-template-list.tsx +++ b/ui/src/app/workflow-templates/workflow-template-list.tsx @@ -12,7 +12,7 @@ import {ExampleManifests} from '../shared/components/example-manifests'; import {InfoIcon} from '../shared/components/fa-icons'; import {Loading} from '../shared/components/loading'; import {PaginationPanel} from '../shared/components/pagination-panel'; -import {Timestamp} from '../shared/components/timestamp'; +import {Timestamp, TimestampSwitch} from '../shared/components/timestamp'; import {useCollectEvent} from '../shared/use-collect-event'; import {ZeroState} from '../shared/components/zero-state'; import {Context} from '../shared/context'; @@ -27,6 +27,7 @@ import {WorkflowTemplateCreator} from './workflow-template-creator'; import {WorkflowTemplateFilters} from './workflow-template-filters'; import './workflow-template-list.scss'; +import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp'; const learnMore = Learn more; @@ -85,6 +86,8 @@ export function WorkflowTemplateList({match, location, history}: RouteComponentP useCollectEvent('openedWorkflowTemplateList'); + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useTimestamp(TIMESTAMP_KEYS.WORKFLOW_TEMPLATE_LIST_CREATION); + return (
NAME
NAMESPACE
-
CREATED
+
+ CREATED +
{templates.map(t => (
{t.metadata.namespace}
- +
))} diff --git a/ui/src/app/workflows/components/events-panel.tsx b/ui/src/app/workflows/components/events-panel.tsx index 3e1fd61bd67c..dae92540688b 100644 --- a/ui/src/app/workflows/components/events-panel.tsx +++ b/ui/src/app/workflows/components/events-panel.tsx @@ -4,11 +4,12 @@ import {map} from 'rxjs/operators'; import {Event} from '../../../models'; import {ErrorNotice} from '../../shared/components/error-notice'; import {Notice} from '../../shared/components/notice'; -import {Timestamp} from '../../shared/components/timestamp'; +import {Timestamp, TimestampSwitch} from '../../shared/components/timestamp'; import {ToggleButton} from '../../shared/components/toggle-button'; import debounce from '../../shared/debounce'; import {ListWatch} from '../../shared/list-watch'; import {services} from '../../shared/services'; +import useTimestamp, {TIMESTAMP_KEYS} from '../../shared/use-timestamp'; export function EventsPanel({namespace, name, kind}: {namespace: string; name: string; kind: string}) { const [showAll, setShowAll] = useState(false); @@ -87,6 +88,8 @@ export function EventsPanel({namespace, name, kind}: {namespace: string; name: s }; }); + const [storedDisplayISOFormat, setStoredDisplayISOFormat] = useTimestamp(TIMESTAMP_KEYS.EVENTS_PANEL_LAST); + return ( <>
@@ -106,7 +109,9 @@ export function EventsPanel({namespace, name, kind}: {namespace: string; name: s
Type
-
Last Seen
+
+ Last Seen +
Reason
Object
Message
@@ -120,7 +125,7 @@ export function EventsPanel({namespace, name, kind}: {namespace: string; name: s {e.type === 'Normal' ? : }
- +
{e.reason}
diff --git a/ui/src/app/workflows/components/workflow-artifacts.tsx b/ui/src/app/workflows/components/workflow-artifacts.tsx index 9b700ec8998b..1f9c820822c1 100644 --- a/ui/src/app/workflows/components/workflow-artifacts.tsx +++ b/ui/src/app/workflows/components/workflow-artifacts.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import * as models from '../../../models'; import {Timestamp} from '../../shared/components/timestamp'; import {services} from '../../shared/services'; +import {TIMESTAMP_KEYS} from '../../shared/use-timestamp'; interface Props { workflow: models.Workflow; @@ -53,7 +54,7 @@ export function WorkflowArtifacts(props: Props) {
{artifact.stepName}
{artifact.path}
- +
))} diff --git a/ui/src/app/workflows/components/workflow-details-list/workflow-details-list.tsx b/ui/src/app/workflows/components/workflow-details-list/workflow-details-list.tsx index 40dbb9e427b4..68f8bef5e90b 100644 --- a/ui/src/app/workflows/components/workflow-details-list/workflow-details-list.tsx +++ b/ui/src/app/workflows/components/workflow-details-list/workflow-details-list.tsx @@ -4,6 +4,8 @@ import * as models from '../../../../models'; import {WorkflowsRow} from '../../../workflows/components/workflows-row/workflows-row'; import './workflow-details-list.scss'; +import useTimestamp, {TIMESTAMP_KEYS} from '../../../shared/use-timestamp'; +import {TimestampSwitch} from '../../../shared/components/timestamp'; interface WorkflowDetailsList { workflows: models.Workflow[]; @@ -11,6 +13,8 @@ interface WorkflowDetailsList { } export function WorkflowDetailsList(props: WorkflowDetailsList) { + const [storedDisplayISOFormatStart, setStoredDisplayISOFormatStart] = useTimestamp(TIMESTAMP_KEYS.CRON_ROW_STARTED); + const [storedDisplayISOFormatFinished, setStoredDisplayISOFormatFinished] = useTimestamp(TIMESTAMP_KEYS.CRON_ROW_FINISHED); return (
@@ -18,8 +22,12 @@ export function WorkflowDetailsList(props: WorkflowDetailsList) {
NAME
NAMESPACE
-
STARTED
-
FINISHED
+
+ STARTED +
+
+ FINISHED +
DURATION
PROGRESS
MESSAGE
@@ -36,7 +44,18 @@ export function WorkflowDetailsList(props: WorkflowDetailsList) {
{/* checkboxes are not visible and are unused in details pages */} {props.workflows.map(wf => { - return ; + return ( + + ); })}
); diff --git a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx index 8d5c70a4ec34..7c4dc96f0a19 100644 --- a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx +++ b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx @@ -21,6 +21,7 @@ import {services} from '../../../shared/services'; import {getResolvedTemplates} from '../../../shared/template-resolution'; import './workflow-node-info.scss'; +import {TIMESTAMP_KEYS} from '../../../shared/use-timestamp'; function nodeDuration(node: models.NodeStatus, now: moment.Moment) { const endTime = node.finishedAt ? moment(node.finishedAt) : now; @@ -82,20 +83,16 @@ const AttributeRows = (props: {attributes: {title: string; value: any}[]}) => (
); -function DisplayWorkflowTime(props: {date: Date | string | number}) { +function DisplayWorkflowTime(props: {date: Date | string | number; timestampKey: TIMESTAMP_KEYS}) { const {date} = props; - const getLocalDateTime = (utc: Date | string | number) => { - return new Date(utc.toString()).toLocaleString(); - }; + + if (date === null || date === undefined) return
-
; + return (
- {date === null || date === undefined ? ( - '-' - ) : ( - - {getLocalDateTime(date)} () - - )} + + +
); } @@ -120,8 +117,8 @@ function WorkflowNodeSummary(props: Props) { } ] : []), - {title: 'START TIME', value: }, - {title: 'END TIME', value: }, + {title: 'START TIME', value: }, + {title: 'END TIME', value: }, { title: 'DURATION', value: {now => } @@ -450,7 +447,7 @@ function WorkflowNodeArtifacts(props: {workflow: Workflow; node: NodeStatus; arc {artifact.path} - +
diff --git a/ui/src/app/workflows/components/workflow-summary-panel.tsx b/ui/src/app/workflows/components/workflow-summary-panel.tsx index 36cade2327e8..85d66735b7b7 100644 --- a/ui/src/app/workflows/components/workflow-summary-panel.tsx +++ b/ui/src/app/workflows/components/workflow-summary-panel.tsx @@ -13,6 +13,7 @@ import {ResourcesDuration} from '../../shared/resources-duration'; import {WorkflowCreatorInfo} from './workflow-creator-info/workflow-creator-info'; import {WorkflowFrom} from './workflow-from'; import {WorkflowLabels} from './workflow-labels/workflow-labels'; +import {TIMESTAMP_KEYS} from '../../shared/use-timestamp'; export const WorkflowSummaryPanel = (props: {workflow: Workflow}) => ( @@ -36,8 +37,8 @@ export const WorkflowSummaryPanel = (props: {workflow: Workflow}) => ( ) }, - {title: 'Started', value: }, - {title: 'Finished ', value: }, + {title: 'Started', value: }, + {title: 'Finished ', value: }, { title: 'Duration', value: ( diff --git a/ui/src/app/workflows/components/workflows-list/workflows-list.scss b/ui/src/app/workflows/components/workflows-list/workflows-list.scss index 01b94241d786..85ec8f2cb346 100644 --- a/ui/src/app/workflows/components/workflows-list/workflows-list.scss +++ b/ui/src/app/workflows/components/workflows-list/workflows-list.scss @@ -32,6 +32,14 @@ height: 100%; } + &__timestamp { + white-space: break-spaces !important; + line-height: normal; + align-items: center; + display: flex; + word-break: break-word; + } + &__title { font-weight: bolder; font-size: 15px; diff --git a/ui/src/app/workflows/components/workflows-list/workflows-list.tsx b/ui/src/app/workflows/components/workflows-list/workflows-list.tsx index d1c9b3ab6630..e994cef30955 100644 --- a/ui/src/app/workflows/components/workflows-list/workflows-list.tsx +++ b/ui/src/app/workflows/components/workflows-list/workflows-list.tsx @@ -29,6 +29,8 @@ import {WorkflowsSummaryContainer} from '../workflows-summary-container/workflow import {WorkflowsToolbar} from '../workflows-toolbar/workflows-toolbar'; import './workflows-list.scss'; +import useTimestamp, {TIMESTAMP_KEYS} from '../../../shared/use-timestamp'; +import {TimestampSwitch} from '../../../shared/components/timestamp'; interface WorkflowListRenderOptions { paginationLimit: number; @@ -160,6 +162,9 @@ export function WorkflowsList({match, location, history}: RouteComponentProps
NAME
NAMESPACE
-
STARTED
-
FINISHED
+
+ STARTED{' '} + +
+
+ FINISHED{' '} + +
DURATION
PROGRESS
MESSAGE
@@ -305,6 +319,8 @@ export function WorkflowsList({match, location, history}: RouteComponentProps ); })} diff --git a/ui/src/app/workflows/components/workflows-row/workflows-row.tsx b/ui/src/app/workflows/components/workflows-row/workflows-row.tsx index b7aba72bf9cb..8e29fd2bc56f 100644 --- a/ui/src/app/workflows/components/workflows-row/workflows-row.tsx +++ b/ui/src/app/workflows/components/workflows-row/workflows-row.tsx @@ -22,6 +22,8 @@ interface WorkflowsRowProps { select: (wf: Workflow) => void; checked: boolean; columns: models.Column[]; + displayISOFormatStart: boolean; + displayISOFormatFinished: boolean; } export function WorkflowsRow(props: WorkflowsRowProps) { @@ -60,11 +62,11 @@ export function WorkflowsRow(props: WorkflowsRowProps) {
{hasAnnotation ? : markdown}
{wf.metadata.namespace}
-
- +
+
-
- +
+
{() => }