Repository: temporalio/ui
Pull request #297 (2022-03-10 21:46:20)

Fix namespace capture and default namespaces
<!--- Note to EXTERNAL Contributors -->
<!-- Thanks for opening a PR! 
If it is a significant code change, please **make sure there is an open issue** for this. 
We work best with you when we have accepted the idea first before you code. -->

<!--- For ALL Contributors 👇 -->

## What was changed
<!-- Describe what has changed in this PR -->

## Why?
<!-- Tell your future self why have you made these changes -->

## Checklist
<!--- add/delete as needed --->

1. Closes <!-- add issue number here -->

2. How was this tested:
<!--- Please describe how you tested your changes/how we can test them -->

3. Any docs updates needed?
<!--- update README if applicable
      or point out where to update docs.temporal.io -->


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>9545697</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://b9152a69.ui-c3x.pages.dev'>https://b9152a69.ui-c3x.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/temporal-pizza/b9152a69-3d65-418d-8e1b-e78abc02aa3d)


Patch:
diff --git a/cypress/integration/check-redirects.spec.js b/cypress/integration/check-redirects.spec.js
new file mode 100644
index 00000000..a7d9fb10
--- /dev/null
+++ b/cypress/integration/check-redirects.spec.js
@@ -0,0 +1,11 @@
+/// <reference types="cypress" />
+
+describe('Workflow Executions List', () => {
+  it('should redirect ', () => {
+    cy.visit('/');
+    cy.url().should(
+      'include',
+      '/namespaces/default/workflows?time-range=24+hours',
+    );
+  });
+});
diff --git a/src/lib/components/select/namespace-select.svelte b/src/lib/components/select/namespace-select.svelte
index 416b991b..0d66f08a 100644
--- a/src/lib/components/select/namespace-select.svelte
+++ b/src/lib/components/select/namespace-select.svelte
@@ -11,7 +11,7 @@
     (namespace) => namespace.namespaceInfo.name,
   );
 
-  let selectedNamespace = $currentNamespace;
+  $: selectedNamespace = $currentNamespace;
 
   function switchNamespace() {
     goto('/namespaces/' + selectedNamespace);
diff --git a/src/lib/stores/namespace.ts b/src/lib/stores/namespace.ts
index 970d8730..9e1679cd 100644
--- a/src/lib/stores/namespace.ts
+++ b/src/lib/stores/namespace.ts
@@ -1,18 +1,21 @@
 import { browser } from '$app/env';
 import { page } from '$app/stores';
-import { derived } from 'svelte/store';
-import UrlPattern from 'url-pattern';
+import { namespaceUrlPattern } from '$lib/utilities/namespace-url-pattern';
+import { derived, get } from 'svelte/store';
+import { settings } from '$lib/stores/settings';
 
-const pattern = new UrlPattern('/namespaces/:namespace/*');
 const onlyInBrowser = <T>(fn: () => T): T => {
   if (browser) return fn();
 };
 
 export const namespace = derived(page, ($page) => {
-  const match = pattern.match($page.url.pathname);
+  const match = namespaceUrlPattern.match($page.url.pathname);
+
+  const defaultNamespace = get(settings).defaultNamespace;
+
   const namespace =
     onlyInBrowser(
-      () => localStorage.getItem('currentNamespace') || 'default',
+      () => localStorage.getItem('currentNamespace') || defaultNamespace,
     ) || 'default';
 
   if (match) {
diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts
index 27713ea6..993e98dc 100644
--- a/src/lib/stores/settings.ts
+++ b/src/lib/stores/settings.ts
@@ -2,6 +2,9 @@ import { writable } from 'svelte/store';
 
 import { fetchSettings } from '$lib/services/settings-service';
 
+/**
+ * We await loadSettings this store in the main __layout so it should already be hydrated to any other part of the app
+ */
 export const settings = writable<Settings | null>(null);
 
 export const loadSettings = async (): Promise<void> => {
diff --git a/src/lib/utilities/namespace-url-pattern.ts b/src/lib/utilities/namespace-url-pattern.ts
new file mode 100644
index 00000000..d692d153
--- /dev/null
+++ b/src/lib/utilities/namespace-url-pattern.ts
@@ -0,0 +1,9 @@
+import UrlPattern from 'url-pattern';
+
+// The default for this lib doesn't include .-_ which are all valid characters for a cloud namespace
+const urlPatternOpts = { segmentValueCharset: '.a-zA-Z0-9_-' };
+
+export const namespaceUrlPattern = new UrlPattern(
+  '/namespaces/:namespace/*',
+  urlPatternOpts,
+);
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 7cb449b7..99a38799 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -40,7 +40,7 @@
     loadCluster().catch(() =>
       notifications.add('error', 'Unable to fetch cluster info'),
     );
-    loadSettings();
+    await loadSettings();
 
     return {
       props: { namespaces },


Test patch:
diff --git a/src/lib/utilities/namespace-url-pattern.test.ts b/src/lib/utilities/namespace-url-pattern.test.ts
new file mode 100644
index 00000000..8288953f
--- /dev/null
+++ b/src/lib/utilities/namespace-url-pattern.test.ts
@@ -0,0 +1,31 @@
+import { namespaceUrlPattern } from './namespace-url-pattern';
+
+describe('Namespace Url Pattern', () => {
+  it('Should match a namespace url', () => {
+    const result = namespaceUrlPattern.match('/namespaces/stuff/url');
+    expect(result).toBeTruthy();
+    expect(result).toEqual({
+      namespace: 'stuff',
+      _: 'url',
+    });
+  });
+
+  it('Should not match a non namespace url', () => {
+    const result = namespaceUrlPattern.match(
+      '/namespaces_butdifferent/stuff/url',
+    );
+    expect(result).toBeNull();
+  });
+
+  it('Should match a namespace with special characters in it like . _ -', () => {
+    const result = namespaceUrlPattern.match(
+      '/namespaces/sergey.temporal-dev_test/workflows/d643b6a9-b73d-4440-b1ec-91234bbc0a11/e8c0acc6-740e-428d-a2e2-c9bb7c081c9c/history/full',
+    );
+
+    expect(result).toBeTruthy();
+    expect(result).toEqual({
+      namespace: 'sergey.temporal-dev_test',
+      _: 'workflows/d643b6a9-b73d-4440-b1ec-91234bbc0a11/e8c0acc6-740e-428d-a2e2-c9bb7c081c9c/history/full',
+    });
+  });
+});