Repository: temporalio/ui
Pull request #421 (2022-04-12 22:12:46)

Add eventTime into compact view
The new tables display all of the attributes, but when we're in the compact view, it shows the date and time for the group. This change adds in the `eventTime` into the the list of attributes when in the compact view.

cloudflare-pages[bot]: ## Deploying with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>40befa8</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://463a1d83.ui-c3x.pages.dev'>https://463a1d83.ui-c3x.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/temporal-pizza/463a1d83-80d3-42a1-adb4-44b3e9aa0f4d)

cypress[bot]: 
<p align="center">
  <br />
  <img src="https://assets.cypress.io/github-pr-comment-icons/cypress-logo.png" height="32" />
</p>

---

### Test summary
<a href="https://dashboard.cypress.io/projects/g7xfxw/runs/127/overview?utm_source=github"><img src="https://assets.cypress.io/github-pr-comment-icons/green-check-mark.png" width="14" height="14" title="Passed" /></a> <b title="Passed">28</b>  &bull; <a href="https://dashboard.cypress.io/projects/g7xfxw/runs/127/test-results?utm_source=github&statuses=%5B%7B%22value%22%3A%22FAILED%22%2C%22label%22%3A%22FAILED%22%7D%5D"><img src="https://assets.cypress.io/github-pr-comment-icons/red-x.png" width="14" height="14" title="Failed" /></a> <b title="Failed">0</b>  &bull; <img src="https://assets.cypress.io/github-pr-comment-icons/gray-circle.png" width="14" height="14" title="Tests that did not run due to a developer annotating a test with .skip" /> <b title="Pending">0</b> &bull; <img src="https://assets.cypress.io/github-pr-comment-icons/yellow-triangle.png" width="14" height="14" title="Tests that did not run due to a failure in a mocha hook" /> <b title="Skipped">0</b> 

---

### Run details
| | |
| --- | --- |
| **Project** | Temporal UI |
| **Status** | Passed |
| **Commit** | 8df18078e2 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span> |
| **Started** | Apr 12, 2022 10:14 PM |
| **Ended** | Apr 12, 2022 10:15 PM |
| **Duration** | 01:28 <a href="https://on.cypress.io/parallelization?utm_source=github" title="Enable parallelization to reduce run duration">💡</a> |
| **OS** | Linux Ubuntu - 20.04 |
| **Browser** | Chrome 100 <img src="https://cdnjs.cloudflare.com/ajax/libs/browser-logos/48.0.4/chrome/chrome_32x32.png" width="16" height="16" /> |

[View run in Cypress Dashboard ➡️](https://dashboard.cypress.io/projects/g7xfxw/runs/127/overview?utm_source=github)





---

This comment has been generated by **cypress-bot** as a result of this project's GitHub integration settings. You can manage this integration in [this project's settings in the Cypress Dashboard](https://dashboard.cypress.io/projects/g7xfxw/settings?utm_source=github)



Patch:
diff --git a/src/lib/components/banner.svelte b/src/lib/components/banner.svelte
index 6b6b6fcd..93362161 100644
--- a/src/lib/components/banner.svelte
+++ b/src/lib/components/banner.svelte
@@ -1,5 +1,6 @@
 <script lang="ts">
   import { closedBannerId, close } from '$lib/stores/banner';
+  import { isRecommendedVersionNewer } from '$lib/utilities/version-check';
 
   export let cluster: ClusterInformation;
 
@@ -13,7 +14,11 @@
   $: current = cluster?.versionInfo?.current;
   $: alert = cluster?.versionInfo?.alerts?.[0];
   $: severity = alert ? severities[alert.severity] : severities.Low;
-  $: show = current?.version && current.version != $closedBannerId;
+  $: show =
+    current?.version &&
+    current.version != $closedBannerId &&
+    isRecommendedVersionNewer(recommended?.version, current.version);
+
   $: message =
     severity == severities.Low
       ? `📥 v${recommended?.version} version is available`
diff --git a/src/lib/components/event/_format-attributes.ts b/src/lib/components/event/_format-attributes.ts
new file mode 100644
index 00000000..9d50f5df
--- /dev/null
+++ b/src/lib/components/event/_format-attributes.ts
@@ -0,0 +1,22 @@
+import { formatDate } from '$lib/utilities/format-date';
+
+type CombinedAttributes = EventAttribute & {
+  eventTime?: string;
+};
+
+const keysToOmit: Readonly<Set<string>> = new Set(['header']);
+
+export const formatAttributes = (
+  event: IterableEvent,
+  { compact } = { compact: false },
+): CombinedAttributes => {
+  const attributes: CombinedAttributes = {};
+
+  if (compact) attributes.eventTime = formatDate(event.eventTime);
+
+  for (const [key, value] of Object.entries(event.attributes)) {
+    if (!keysToOmit.has(key)) attributes[key] = value;
+  }
+
+  return attributes;
+};
diff --git a/src/lib/components/event/event-details.svelte b/src/lib/components/event/event-details.svelte
index 49bfaac6..ea7936ce 100644
--- a/src/lib/components/event/event-details.svelte
+++ b/src/lib/components/event/event-details.svelte
@@ -5,14 +5,19 @@
   } from '$lib/utilities/get-single-attribute-for-event';
   import EventDetailsRow from './event-details-row.svelte';
 
+  import { formatAttributes } from './_format-attributes';
+
   export let event: IterableEvent;
   export let summaryEvent = event;
   export let expanded = false;
+  export let compact = false;
+
+  $: attributes = formatAttributes(event, { compact });
 </script>
 
 <section>
   {#if expanded}
-    {#each Object.entries(event.attributes) as [key, value] (key)}
+    {#each Object.entries(attributes) as [key, value] (key)}
       {#if shouldDisplayAttribute(key, value)}
         <EventDetailsRow {key} {value} />
       {/if}
diff --git a/src/lib/components/event/event-summary-row.svelte b/src/lib/components/event/event-summary-row.svelte
index 6856bcc8..28a7149d 100644
--- a/src/lib/components/event/event-summary-row.svelte
+++ b/src/lib/components/event/event-summary-row.svelte
@@ -46,7 +46,12 @@
   </div>
 
   <div class="cell links">
-    <EventDetails event={currentEvent} {expanded} summaryEvent={event} />
+    <EventDetails
+      event={currentEvent}
+      summaryEvent={event}
+      {expanded}
+      {compact}
+    />
   </div>
 </article>
 
diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts
index e2c98f89..29b02334 100644
--- a/src/lib/utilities/route-for-api.ts
+++ b/src/lib/utilities/route-for-api.ts
@@ -30,18 +30,31 @@ export function routeForApi(
   route: APIRoutePath,
   parameters?: APIRouteParameters,
 ): string {
+  const encoded: APIRouteParameters = Object.keys(parameters ?? {}).reduce(
+    (acc, key) => {
+      acc[key] = encodeURIComponent(parameters[key]);
+      return acc;
+    },
+    {
+      namespace: '',
+      workflowId: '',
+      runId: '',
+      queue: '',
+    },
+  );
+
   const routes: { [K in APIRoutePath]: string } = {
     cluster: '/cluster',
     settings: '/settings',
     user: '/me',
     namespaces: '/namespaces',
-    'task-queue': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}`,
-    workflows: `/namespaces/${parameters?.namespace}/workflows`,
-    'workflows.archived': `/namespaces/${parameters?.namespace}/workflows/archived`,
-    workflow: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}`,
-    'workflow.terminate': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/terminate`,
-    events: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/events`,
-    query: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/query`,
+    'task-queue': `/namespaces/${encoded?.namespace}/task-queues/${encoded?.queue}`,
+    workflows: `/namespaces/${encoded?.namespace}/workflows`,
+    'workflows.archived': `/namespaces/${encoded?.namespace}/workflows/archived`,
+    workflow: `/namespaces/${encoded?.namespace}/workflows/${encoded?.workflowId}/runs/${encoded?.runId}`,
+    'workflow.terminate': `/namespaces/${encoded?.namespace}/workflows/${encoded?.workflowId}/runs/${encoded?.runId}/terminate`,
+    events: `/namespaces/${encoded?.namespace}/workflows/${encoded?.workflowId}/runs/${encoded?.runId}/events`,
+    query: `/namespaces/${encoded?.namespace}/workflows/${encoded?.workflowId}/runs/${encoded?.runId}/query`,
   };
 
   return withBase(routes[route]);
diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts
index d09aa534..f6b02fe4 100644
--- a/src/lib/utilities/route-for.ts
+++ b/src/lib/utilities/route-for.ts
@@ -58,7 +58,9 @@ export const routeForWorkflow = ({
   run,
   ...parameters
 }: WorkflowParameters): string => {
-  return `${routeForWorkflows(parameters)}/${workflow}/${run}`;
+  const wid = encodeURIComponent(workflow);
+
+  return `${routeForWorkflows(parameters)}/${wid}/${run}`;
 };
 
 export const routeForEventHistory = ({
diff --git a/src/lib/utilities/version-check.ts b/src/lib/utilities/version-check.ts
new file mode 100644
index 00000000..f03c4ed2
--- /dev/null
+++ b/src/lib/utilities/version-check.ts
@@ -0,0 +1,19 @@
+export const isRecommendedVersionNewer = (
+  recommendedVersion: string,
+  currentVersion: string,
+) => {
+  if (!recommendedVersion || !currentVersion) {
+    return false;
+  }
+
+  const [major1, minor1, patch1] = recommendedVersion.split('.').map(Number);
+  const [major2, minor2, patch2] = currentVersion.split('.').map(Number);
+
+  if (major1 !== major2) {
+    return major1 > major2;
+  } else if (minor1 !== minor2) {
+    return minor1 > minor2;
+  } else {
+    return patch1 > patch2;
+  }
+};
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
index 73bf7397..abc13160 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
@@ -10,7 +10,7 @@
 
     const parameters = {
       namespace,
-      workflowId,
+      workflowId: decodeURIComponent(workflowId),
       runId,
     };
 


Test patch:
diff --git a/src/lib/utilities/route-for-api.test.ts b/src/lib/utilities/route-for-api.test.ts
index 0b28e7f8..aea2c3b7 100644
--- a/src/lib/utilities/route-for-api.test.ts
+++ b/src/lib/utilities/route-for-api.test.ts
@@ -55,7 +55,7 @@ describe('API Request Encoding', () => {
         workflowId: 'worflow#with#hashes',
       }),
     ).toBe(
-      'http://localhost:8080/api/v1/namespaces/namespace/workflows/worflow%23with%23hashes/runs/run',
+      'http://localhost:8080/api/v1/namespaces/namespace/workflows/worflow%2523with%2523hashes/runs/run',
     );
   });
 });
diff --git a/src/lib/utilities/version-check.test.ts b/src/lib/utilities/version-check.test.ts
new file mode 100644
index 00000000..9b247d61
--- /dev/null
+++ b/src/lib/utilities/version-check.test.ts
@@ -0,0 +1,25 @@
+import { isRecommendedVersionNewer } from './version-check';
+
+describe(isRecommendedVersionNewer, () => {
+  it('should return true when recommended version is higher than current', () => {
+    expect(isRecommendedVersionNewer('2.01.1', '1.01.1')).toBe(true);
+    expect(isRecommendedVersionNewer('1.02.1', '1.01.1')).toBe(true);
+    expect(isRecommendedVersionNewer('1.01.2', '1.01.1')).toBe(true);
+    expect(isRecommendedVersionNewer('1.02', '1.01')).toBe(true);
+    expect(isRecommendedVersionNewer('2', '1')).toBe(true);
+  });
+
+  it('should return false when recommended version is not higher than current', () => {
+    expect(isRecommendedVersionNewer('1.01.1', '1.01.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.01.1', '2.01.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.01.1', '1.02.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.01.1', '1.01.2')).toBe(false);
+    expect(isRecommendedVersionNewer(undefined, '1.01.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.01.1', undefined)).toBe(false);
+    expect(isRecommendedVersionNewer(undefined, undefined)).toBe(false);
+    expect(isRecommendedVersionNewer('xxx', '1.01.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.1', '1.01.1')).toBe(false);
+    expect(isRecommendedVersionNewer('1.01', '1.02')).toBe(false);
+    expect(isRecommendedVersionNewer('1', '2')).toBe(false);
+  });
+});