Skip to content

Commit

Permalink
feat(ui): Support expression evaluation in links (#5666)
Browse files Browse the repository at this point in the history
  • Loading branch information
dinever authored Apr 15, 2021
1 parent 24ac725 commit a018523
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 4 deletions.
28 changes: 27 additions & 1 deletion docs/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,31 @@ You can configure Argo Server to show custom links:
* A "Get Help" button in the bottom right of the window linking to you organisation help pages or chat room.
* Deep-links to your facilities (e.g. logging facility) in the user interface for both the workflow and each workflow pod.

See [workflow-controller-configmap.yaml](workflow-controller-configmap.yaml)
Links can contain placeholder variables. Placeholder variables are indicated by the dollar sign and curly braces: `${variable}`.

These are the commonly used variables:

- `${metadata.namespace}`: Kubernetes namespace of the current workflow / pod / event source / sensor
- `${metadata.name}`: Name of the current workflow / pod / event source / sensor
- `${status.startedAt}`: Start timestamp of the workflow / pod, in the format of `2021-01-01T10:35:56Z`
- `${status.finishedAt}`: End timestamp of the workflow / pod, in the format of `2021-01-01T10:35:56Z`. If the workflow/pod is still running, this variable will be `null`

See [workflow-controller-configmap.yaml](workflow-controller-configmap.yaml) for a complete example

> v3.1 and after
Epoch timestamps are available now. These are useful if we want to add links to logging facilities like [Grafana](https://grafana.com/)
or [DataDog](https://datadoghq.com/), as they support Unix epoch timestamp formats as URL
parameters:

- `${status.startedAtEpoch}`: Start timestamp of the workflow/pod, in the Unix epoch time format in **milliseconds**, e.g. `1609497000000`.
- `${status.finishedAtEpoch}`: End timestamp of the workflow/pod, in the Unix epoch time format in **milliseconds**, e.g. `1609497000000`. If the workflow/pod is still running, this variable will represent the currnet time.

> v3.1 and after
In addition to the above variables, we can now access all [workflow fields](fields.md#workflow) under `${workflow}`.

For example, one may find it useful to define a custom label in the workflow and access it by `${workflow.metadata.labels.custom_label_name}`

We can also access workflow fields in a pod link. For example, `${workflow.metadata.name}` returns
the name of the workflow instead of the name of the pod.
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
finishedAt: this.state.workflow.status.finishedAt
}
};
document.location.href = ProcessURL(link.url, object);
const url = ProcessURL(link.url, object);

if ((window.event as MouseEvent).ctrlKey || (window.event as MouseEvent).metaKey) {
window.open(url, '_blank');
} else {
document.location.href = url;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {useEffect, useState} from 'react';
import {Observable} from 'rxjs';
import {EventSource} from '../../../models';
import {ErrorNotice} from '../../shared/components/error-notice';
import {Links} from '../../shared/components/links';
import {services} from '../../shared/services';
import {FullHeightLogsViewer} from '../../workflows/components/workflow-logs-viewer/full-height-logs-viewer';

Expand Down Expand Up @@ -99,6 +100,7 @@ export const EventSourceLogsViewer = ({
}}
/>
)}
<Links scope='event-source-logs' object={eventSource} />
</div>
</div>
</div>
Expand Down
46 changes: 46 additions & 0 deletions ui/src/app/shared/components/links.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {ProcessURL} from './links';

describe('process URL', () => {
test('original timestamp', () => {
const object = {
status: {
startedAt: '2021-01-01T10:30:00Z',
finishedAt: '2021-01-01T10:30:00Z'
}
};
expect(ProcessURL('https://logging?from=${status.startedAt}&to=${status.finishedAt}', object)).toBe('https://logging?from=2021-01-01T10:30:00Z&to=2021-01-01T10:30:00Z');
});

test('epoch timestamp', () => {
const object = {
status: {
startedAt: '2021-01-01T10:30:00Z',
finishedAt: '2021-01-01T10:30:00Z'
}
};
expect(ProcessURL('https://logging?from=${status.startedAtEpoch}&to=${status.finishedAtEpoch}', object)).toBe('https://logging?from=1609497000000&to=1609497000000');
});

test('epoch timestamp with ongoing workflow', () => {
const object = {
status: {
startedAt: '2021-01-01T10:30:00Z'
}
};

const expectedDate = new Date('2021-03-01T10:30:00.00Z');
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => expectedDate.valueOf());

expect(ProcessURL('https://logging?from=${status.startedAtEpoch}&to=${status.finishedAtEpoch}', object)).toBe(
`https://logging?from=1609497000000&to=${expectedDate.getTime()}`
);
});

test('no timestamp', () => {
const object = {
status: {}
};

expect(ProcessURL('https://logging?from=${status.startedAtEpoch}&to=${status.finishedAtEpoch}', object)).toBe(`https://logging?from=null&to=null`);
});
});
19 changes: 18 additions & 1 deletion ui/src/app/shared/components/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ import {Link, Workflow} from '../../../models';
import {services} from '../services';
import {Button} from './button';

const addEpochTimestamp = (jsonObject: {metadata: ObjectMeta; workflow?: Workflow; status?: any}) => {
if (jsonObject === undefined || jsonObject.status.startedAt === undefined) {
return;
}

const toEpoch = (datetime: string) => {
if (datetime) {
return new Date(datetime).getTime();
} else {
return Date.now();
}
};
jsonObject.status.startedAtEpoch = toEpoch(jsonObject.status.startedAt);
jsonObject.status.finishedAtEpoch = toEpoch(jsonObject.status.finishedAt);
};

export const ProcessURL = (url: string, jsonObject: any) => {
addEpochTimestamp(jsonObject);
/* replace ${} from input url with corresponding elements from object
return null if element is not found*/
return url.replace(/\${[^}]*}/g, x => {
Expand Down Expand Up @@ -34,7 +51,7 @@ export const Links = ({scope, object, button}: {scope: string; object: {metadata
};

const openLink = (url: string) => {
if ((window.event as MouseEvent).ctrlKey) {
if ((window.event as MouseEvent).ctrlKey || (window.event as MouseEvent).metaKey) {
window.open(url, '_blank');
} else {
document.location.href = url;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export const WorkflowDetails = ({history, location, match}: RouteComponentProps<
};
const url = ProcessURL(link.url, object);

if ((window.event as MouseEvent).ctrlKey) {
if ((window.event as MouseEvent).ctrlKey || (window.event as MouseEvent).metaKey) {
window.open(url, '_blank');
} else {
document.location.href = url;
Expand Down

0 comments on commit a018523

Please sign in to comment.