Repository: temporalio/ui
Pull request #89 (2021-09-24 18:46:54)

Fix bugs with query store and activity view
Further refinements to how we update data. Reactive updating to current activity view.


Patch:
diff --git a/src/lib/services/workflow-execution-service.ts b/src/lib/services/workflow-execution-service.ts
index cbd050e9..6bf55bab 100644
--- a/src/lib/services/workflow-execution-service.ts
+++ b/src/lib/services/workflow-execution-service.ts
@@ -44,31 +44,23 @@ const fetchWorkflows =
     startTime = { days: 1 },
     request = fetch,
   }: FetchWorkflows<ListWorkflowExecutionsResponse>) => {
-    const { executions } = await paginated(async (token: string) => {
-      const iso = formatISO(sub(new Date(), startTime));
-      const url = toURL(`${base}/namespaces/${namespace}/workflows/${type}`, {
-        next_page_token: token,
-        'start_time_filter.earliest_time': iso,
-      });
+    const { executions } = await paginated(
+      async (token: string) => {
+        const iso = formatISO(sub(new Date(), startTime));
+        const url = toURL(`${base}/namespaces/${namespace}/workflows/${type}`, {
+          next_page_token: token,
+          'start_time_filter.earliest_time': iso,
+        });
 
-      const response = await request(url);
-      return await response.json();
-    }, onUpdate);
+        const response = await request(url);
+        return await response.json();
+      },
+      { onUpdate },
+    );
 
     return executions;
   };
 
-export const fetchOpenWorkflows = fetchWorkflows('open');
-export const fetchClosedWorkflows = fetchWorkflows('closed');
-export const fetchAllWorkflows = (
-  options:
-    | Parameters<typeof fetchOpenWorkflows>[0]
-    | Parameters<typeof fetchClosedWorkflows>[0],
-) => {
-  fetchOpenWorkflows(options);
-  fetchClosedWorkflows(options);
-};
-
 export const fetchEvents = async ({
   namespace,
   executionId,
@@ -88,12 +80,23 @@ export const fetchEvents = async ({
       const response = await request(url);
       return await response.json();
     },
-    onUpdate,
+    { onUpdate },
   );
 
   return events;
 };
 
+export const fetchOpenWorkflows = fetchWorkflows('open');
+export const fetchClosedWorkflows = fetchWorkflows('closed');
+export const fetchAllWorkflows = (
+  options:
+    | Parameters<typeof fetchOpenWorkflows>[0]
+    | Parameters<typeof fetchClosedWorkflows>[0],
+) => {
+  fetchOpenWorkflows(options);
+  fetchClosedWorkflows(options);
+};
+
 export const WorkflowExecutionAPI = {
   async getAll(
     { namespace }: NamespaceScopedRequest,
diff --git a/src/lib/stores/workflows.ts b/src/lib/stores/workflows.ts
index cc7c9ea2..19f31fea 100644
--- a/src/lib/stores/workflows.ts
+++ b/src/lib/stores/workflows.ts
@@ -1,4 +1,5 @@
 import { derived, writable, Writable, get as getFromStore } from 'svelte/store';
+
 import type { ListWorkflowExecutionsResponse } from '$types';
 import { unique } from '$lib/utilities/unique';
 
@@ -35,8 +36,8 @@ const updateWorkflows =
 
     store.update(($store) => ({
       ...$store,
-      ids: Object.keys(ids),
-      workflows,
+      ids: [...$store.ids, ...Object.keys(ids)],
+      workflows: { ...$store.workflows, ...workflows },
     }));
   };
 
@@ -57,6 +58,15 @@ export const createStore = (namespace: string) => {
   const ids = derived(store, ($store) => Object.keys($store.ids));
   const range = createStoreWithCallback<Duration>({ hours: 24 }, update);
 
+  range.subscribe(() =>
+    store.set({
+      loading: false,
+      updating: true,
+      workflows: {},
+      ids: [],
+    }),
+  );
+
   const status = writable<WorkflowStatus>(null);
   const workflowType = writable<WorkflowType>(null);
   const executionId = writable<string>(null);
diff --git a/src/lib/utilities/paginated.ts b/src/lib/utilities/paginated.ts
index 618d8ed8..e8f0e6d6 100644
--- a/src/lib/utilities/paginated.ts
+++ b/src/lib/utilities/paginated.ts
@@ -1,22 +1,45 @@
+import isFunction from 'lodash/isFunction';
 import { merge } from './merge';
 
 type NextPageToken = Uint8Array | string;
 type WithNextPageToken = { nextPageToken?: NextPageToken };
+type PaginatedOptions<T> = {
+  onStart?: () => void;
+  onUpdate?: (
+    full: Omit<T, keyof WithNextPageToken>,
+    current: Omit<T, keyof WithNextPageToken>,
+  ) => void;
+  onComplete?: (finalProps: Omit<T, keyof WithNextPageToken>) => void;
+  token?: NextPageToken;
+  previousProps?: Omit<T, keyof WithNextPageToken>;
+};
 
 export const paginated = async <T extends WithNextPageToken>(
   fn: (token?: NextPageToken) => Promise<T>,
-  onUpdate?: (
-    current: Omit<T, keyof WithNextPageToken>,
-    full: Omit<T, keyof WithNextPageToken>,
-  ) => void,
-  token?: NextPageToken,
-  previousProps?: Omit<T, keyof WithNextPageToken>,
+  {
+    onStart,
+    onUpdate,
+    onComplete,
+    token,
+    previousProps,
+  }: PaginatedOptions<T> = {},
 ): Promise<Omit<T, keyof WithNextPageToken>> => {
+  if (!previousProps && isFunction(onStart)) onStart();
+
   const { nextPageToken, ...props } = await fn(token);
   const mergedProps = merge(previousProps, props);
 
-  if (typeof onUpdate === 'function') onUpdate(props, mergedProps);
-  if (!nextPageToken) return mergedProps;
+  if (isFunction(onUpdate)) onUpdate(mergedProps, props);
+  if (!nextPageToken) {
+    if (isFunction(onComplete)) onComplete(mergedProps);
+    return mergedProps;
+  }
 
-  return paginated(fn, onUpdate, nextPageToken, mergedProps);
+  return paginated(fn, {
+    onStart,
+    onUpdate,
+    onComplete,
+    token: nextPageToken,
+    previousProps: mergedProps,
+  });
 };
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
index 0c882dac..1acf8c5a 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout.svelte
@@ -68,5 +68,7 @@
   .sidebar {
     width: 600px;
     overflow-y: scroll;
+    box-shadow: -5px 5px 10px 2px rgba(0, 0, 0, 0.2);
+    z-index: 2;
   }
 </style>
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/activities/[activity].svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/activities/[activity].svelte
index 61e83503..caf9b048 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/activities/[activity].svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/activities/[activity].svelte
@@ -32,7 +32,7 @@
   export let activity: string;
 
   const { activities } = createEventStore(namespace, executionId, runId);
-  const selectedActivity = activities.getActivity(activity);
+  $: selectedActivity = activities.getActivity(activity);
 
   $: events = Object.keys(omit($selectedActivity, ['id', 'status']));
 </script>


Test patch:
diff --git a/src/lib/utilities/paginated.test.ts b/src/lib/utilities/paginated.test.ts
index 646f4b23..2c1f16b0 100644
--- a/src/lib/utilities/paginated.test.ts
+++ b/src/lib/utilities/paginated.test.ts
@@ -41,7 +41,9 @@ describe(paginated, () => {
 
   it('should collect incremental updates with an onUpdate callback', () => {
     const mockCallback = jest.fn((x) => x);
-    return paginated((token) => fetchMock(token), mockCallback).then(() => {
+    return paginated((token) => fetchMock(token), {
+      onUpdate: mockCallback,
+    }).then(() => {
       expect(mockCallback.mock.calls.length).toBe(4);
     });
   });