Repository: temporalio/ui
Pull request #412 (2022-04-11 17:57:18)

Check if default namespace from settings exists in namespaces
## What was changed
We were setting the default namespace based on settings.DefaultNamespace or if that doesn't exist, set to 'default'. But if a user has namespace(s) that doesn't include the settings.DefaultNamespace or 'default', it would return a 404.

## Why?
To check for fetched namespaces before setting the defaultNamespace in stuff.


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>d365878</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>⚡️&nbsp; Build in progress...</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/temporal-pizza/c3184d51-dac9-4048-806f-7016be61cc3b)

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/97/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/97/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** | 182d826713 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span> |
| **Started** | Apr 11, 2022 5:58 PM |
| **Ended** | Apr 11, 2022 5:59 PM |
| **Duration** | 00:58 <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/97/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/README.md b/README.md
index 57c85ace..6c703e62 100644
--- a/README.md
+++ b/README.md
@@ -60,9 +60,9 @@ Set these environment variables if you want to change their defaults
 
 ## Developing with Canary
 
-To get a better representation of production data, you can run our UI with the go-canary repo. You will need go installed on your machine.
+To get a better representation of production data, you can run our UI with the [canary-go](https://github.com/temporalio/canary-go) repo. You will need go installed on your machine.
 
-### go-canary
+### canary-go
 
 ```bash
 make bins
diff --git a/src/lib/utilities/get-namespace.ts b/src/lib/utilities/get-namespace.ts
index e014fecc..d73aaec0 100644
--- a/src/lib/utilities/get-namespace.ts
+++ b/src/lib/utilities/get-namespace.ts
@@ -6,6 +6,11 @@ type GetNamespaceParameters = {
   namespaces: DescribeNamespaceResponse[];
 };
 
+type GetDefaultNamespaceParameters = {
+  namespaces: DescribeNamespaceResponse[];
+  settings: Settings;
+};
+
 export const getNamespace = ({
   namespace,
   defaultNamespace,
@@ -21,3 +26,25 @@ export const getNamespace = ({
 
   return defaultNamespace;
 };
+
+export const getDefaultNamespace = ({
+  namespaces,
+  settings,
+}: GetDefaultNamespaceParameters): string => {
+  const { showTemporalSystemNamespace, defaultNamespace } = settings;
+
+  const namespaceNames = namespaces
+    .map(
+      (namespace: DescribeNamespaceResponse) => namespace?.namespaceInfo?.name,
+    )
+    .filter(
+      (namespace: string) =>
+        showTemporalSystemNamespace || namespace !== 'temporal-system',
+    );
+
+  return (
+    namespaceNames.find((ns) => ns === defaultNamespace) ??
+    namespaceNames[0] ??
+    defaultNamespace
+  );
+};
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index b54c3cc0..b2ce3e1f 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -9,6 +9,7 @@
   import { fetchUser } from '$lib/services/user-service';
   import { fetchCluster } from '$lib/services/cluster-service';
   import { fetchNamespaces } from '$lib/services/namespaces-service';
+  import { getDefaultNamespace } from '$lib/utilities/get-namespace';
 
   export const load: Load = async function ({ url, fetch }) {
     const settings: Settings = await fetchSettings({ url }, fetch);
@@ -18,12 +19,13 @@
       fetch,
     );
 
+    const defaultNamespace = getDefaultNamespace({ namespaces, settings });
     const user = await fetchUser(fetch);
     const cluster = await fetchCluster(settings, fetch);
 
     return {
-      props: { namespaces, user, cluster },
-      stuff: { namespaces, settings },
+      props: { user, cluster },
+      stuff: { namespaces, settings: { ...settings, defaultNamespace } },
     };
   };
 </script>
diff --git a/src/routes/_header.svelte b/src/routes/_header.svelte
index b75dbf40..c5c35876 100644
--- a/src/routes/_header.svelte
+++ b/src/routes/_header.svelte
@@ -11,7 +11,8 @@
   import NavigationLink from './_navigation-link.svelte';
   import IsCloudGuard from '$lib/components/is-cloud-guard.svelte';
 
-  $: namespace = $page.params.namespace;
+  $: namespace =
+    $page.params.namespace || $page.stuff?.settings?.defaultNamespace;
 
   export let user: User;
 </script>


Test patch:
diff --git a/src/lib/utilities/get-namespace.test.ts b/src/lib/utilities/get-namespace.test.ts
new file mode 100644
index 00000000..9b256c78
--- /dev/null
+++ b/src/lib/utilities/get-namespace.test.ts
@@ -0,0 +1,172 @@
+import { getDefaultNamespace } from './get-namespace';
+
+const temporalSystemNamespace = {
+  namespaceInfo: {
+    name: 'temporal-system',
+    state: 'Registered',
+    description: 'Temporal internal system namespace',
+    ownerEmail: 'temporal-core@temporal.io',
+    data: {},
+    id: '32049b68-7872-4094-8e63-d0dd59896a83',
+  },
+  config: {
+    workflowExecutionRetentionTtl: '604800s',
+    badBinaries: {
+      binaries: {},
+    },
+    historyArchivalState: 'Disabled',
+    historyArchivalUri: '',
+    visibilityArchivalState: 'Disabled',
+    visibilityArchivalUri: '',
+  },
+  replicationConfig: {
+    activeClusterName: 'active',
+    clusters: [
+      {
+        clusterName: 'active',
+      },
+    ],
+    state: 'Unspecified',
+  },
+  failoverVersion: '0',
+  isGlobalNamespace: false,
+};
+
+const canaryNamespace = {
+  namespaceInfo: {
+    name: 'canary',
+    state: 'Registered',
+    description: 'Namespace for running temporal canary workflows',
+    ownerEmail: 'canary',
+    data: {},
+    id: '6879d26a-d9ad-486b-a572-b11aeed816e8',
+  },
+  config: {
+    workflowExecutionRetentionTtl: '864000s',
+    badBinaries: {
+      binaries: {},
+    },
+    historyArchivalState: 'Disabled',
+    historyArchivalUri: '',
+    visibilityArchivalState: 'Disabled',
+    visibilityArchivalUri: '',
+  },
+  replicationConfig: {
+    activeClusterName: 'active',
+    clusters: [
+      {
+        clusterName: 'active',
+      },
+    ],
+    state: 'Unspecified',
+  },
+  failoverVersion: '0',
+  isGlobalNamespace: false,
+};
+
+const defaultNamespace = {
+  namespaceInfo: {
+    name: 'default',
+    state: 'Registered',
+    description: 'Default namespace',
+    ownerEmail: 'default',
+    data: {},
+    id: '6879d26a-d9ad-486b-a572-b11aeed816e8',
+  },
+  config: {
+    workflowExecutionRetentionTtl: '864000s',
+    badBinaries: {
+      binaries: {},
+    },
+    historyArchivalState: 'Disabled',
+    historyArchivalUri: '',
+    visibilityArchivalState: 'Disabled',
+    visibilityArchivalUri: '',
+  },
+  replicationConfig: {
+    activeClusterName: 'active',
+    clusters: [
+      {
+        clusterName: 'active',
+      },
+    ],
+    state: 'Unspecified',
+  },
+  failoverVersion: '0',
+  isGlobalNamespace: false,
+};
+
+const getSettings = (
+  defaultNamespace: string,
+  showTemporalSystemNamespace: boolean,
+) => {
+  return {
+    auth: {
+      enabled: false,
+      options: ['organization', 'invitation', 'audience'],
+    },
+    defaultNamespace,
+    showTemporalSystemNamespace,
+  };
+};
+
+describe(getDefaultNamespace, () => {
+  it('should return default namespace if it exists in namespaces and matches defaultNamespace setting', () => {
+    const namespaces = [defaultNamespace];
+    const settings = getSettings('default', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('default');
+  });
+  it('should return default namespace if it exists in namespaces and no defaultNamespace setting', () => {
+    const namespaces = [defaultNamespace];
+    const settings = getSettings('', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('default');
+  });
+  it('should return default namespace if it exists in namespaces and includes temporal system namespace with !ShowTemporalSystemNamespace', () => {
+    const namespaces = [temporalSystemNamespace, defaultNamespace];
+    const settings = getSettings('', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('default');
+  });
+  it('should return default namespace if it exists in namespaces and includes temporal system namespace with ShowTemporalSystemNamespace', () => {
+    const namespaces = [temporalSystemNamespace, defaultNamespace];
+    const settings = getSettings('', true);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual(
+      'temporal-system',
+    );
+  });
+  it('should return canary namespace if single namespace that does not match defaultNamespace setting', () => {
+    const namespaces = [canaryNamespace];
+    const settings = getSettings('default', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('canary');
+  });
+  it('should return default namespace if multiple namespaces and matching defaultNamespace setting', () => {
+    const namespaces = [canaryNamespace, defaultNamespace];
+    const settings = getSettings('default', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('default');
+  });
+  it('should return canary namespace if multiple namespaces and matching defaultNamespace setting', () => {
+    const namespaces = [canaryNamespace, defaultNamespace];
+    const settings = getSettings('canary', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('canary');
+  });
+  it('should return first namespace if multiple namespaces and no defaultNamespace setting', () => {
+    const namespaces = [canaryNamespace, defaultNamespace];
+    const settings = getSettings('', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('canary');
+  });
+  it('should return first namespace if multiple namespaces and no defaultNamespace setting and including ShowTemporalSystemNamespace', () => {
+    const namespaces = [
+      temporalSystemNamespace,
+      canaryNamespace,
+      defaultNamespace,
+    ];
+    const settings = getSettings('', true);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual(
+      'temporal-system',
+    );
+  });
+  it('should return defaultNamespace setting if no namespaces', () => {
+    const namespaces = [];
+    const settings = getSettings('default', false);
+    expect(getDefaultNamespace({ namespaces, settings })).toEqual('default');
+  });
+});