Repository: temporalio/ui
Pull request #168 (2021-12-10 22:13:05)

Add event filtering
Adds event filtering using the logic—or something close to it—as found in the existing UI.


Patch:
diff --git a/src/events.d.ts b/src/events.d.ts
index 424137e0..0c7212de 100644
--- a/src/events.d.ts
+++ b/src/events.d.ts
@@ -303,11 +303,64 @@ type EventAttribute =
   | WorkflowTaskFailedAttrs
   | ChildWorkflowExecutionFailedAttrs;
 
-interface HistoryEventWithId extends HistoryEvent, Record<string, unknown> {
+interface HistoryEventWithId
+  extends Omit<HistoryEvent, 'eventType'>,
+    Record<string, unknown> {
   id: string;
+  eventType: EventType;
   attributes: EventAttribute;
 }
 
 type PendingActivity = Omit<import('$types').PendingActivityInfo, 'state'> & {
   state: 'Unspecified' | 'Scheduled' | 'Started' | 'CancelRequested';
 } & Record<string, unknown>;
+
+type EventType =
+  | 'ActivityTaskCanceled'
+  | 'ActivityTaskCancelRequested'
+  | 'ActivityTaskCompleted'
+  | 'ActivityTaskFailed'
+  | 'ActivityTaskScheduled'
+  | 'ActivityTaskStarted'
+  | 'ActivityTaskTimedOut'
+  | 'ChildWorkflowExecutionCanceled'
+  | 'ChildWorkflowExecutionCompleted'
+  | 'ChildWorkflowExecutionFailed'
+  | 'ChildWorkflowExecutionStarted'
+  | 'ChildWorkflowExecutionTerminated'
+  | 'ChildWorkflowExecutionTimedOut'
+  | 'StartChildWorkflowExecutionFailed'
+  | 'StartChildWorkflowExecutionInitiated'
+  | 'SignalExternalWorkflowExecutionFailed'
+  | 'SignalExternalWorkflowExecutionInitiated'
+  | 'TimerCanceled'
+  | 'TimerFired'
+  | 'TimerStarted'
+  | 'WorkflowExecutionCanceled'
+  | 'WorkflowExecutionCancelRequested'
+  | 'WorkflowExecutionCompleted'
+  | 'WorkflowExecutionContinuedAsNew'
+  | 'WorkflowExecutionFailed'
+  | 'WorkflowExecutionSignaled'
+  | 'WorkflowExecutionStarted'
+  | 'WorkflowExecutionTerminated'
+  | 'WorkflowExecutionTimedOut'
+  | 'WorkflowTaskCompleted'
+  | 'WorkflowTaskFailed'
+  | 'WorkflowTaskScheduled'
+  | 'WorkflowTaskStarted'
+  | 'WorkflowTaskTimedOut'
+  | 'ExternalWorkflowExecutionCancelRequested'
+  | 'ExternalWorkflowExecutionSignaled'
+  | 'RequestCancelExternalWorkflowExecutionFailed'
+  | 'RequestCancelExternalWorkflowExecutionInitiated'
+  | 'MarkerRecorded'
+  | 'UpsertWorkflowSearchAttributes';
+
+type EventTypeCategory =
+  | 'activity'
+  | 'child-workflow'
+  | 'signal'
+  | 'timer'
+  | 'workflow'
+  | 'command';
diff --git a/src/lib/models/event-history.ts b/src/lib/models/event-history.ts
index 5f135057..f2c956d4 100644
--- a/src/lib/models/event-history.ts
+++ b/src/lib/models/event-history.ts
@@ -13,9 +13,12 @@ export function getEventAttributes(historyEvent: HistoryEvent): EventAttribute {
 export const toEventHistory = (
   response: GetWorkflowExecutionHistoryResponse,
 ): HistoryEventWithId[] => {
-  return response.history.events.map((event) => ({
-    ...event,
-    id: String(event.eventId),
-    attributes: getEventAttributes(event),
-  }));
+  return response.history.events.map(
+    (event): HistoryEventWithId => ({
+      ...event,
+      id: String(event.eventId),
+      eventType: event.eventType as unknown as EventType,
+      attributes: getEventAttributes(event),
+    }),
+  );
 };
diff --git a/src/lib/utilities/get-event-categorization.ts b/src/lib/utilities/get-event-categorization.ts
new file mode 100644
index 00000000..1921aabb
--- /dev/null
+++ b/src/lib/utilities/get-event-categorization.ts
@@ -0,0 +1,70 @@
+export const eventTypeCategorizations: Record<EventType, EventTypeCategory> = {
+  ActivityTaskCanceled: 'activity',
+  ActivityTaskCancelRequested: 'activity',
+  ActivityTaskCompleted: 'activity',
+  ActivityTaskFailed: 'activity',
+  ActivityTaskScheduled: 'activity',
+  ActivityTaskStarted: 'activity',
+  ActivityTaskTimedOut: 'activity',
+
+  ChildWorkflowExecutionCanceled: 'child-workflow',
+  ChildWorkflowExecutionCompleted: 'child-workflow',
+  ChildWorkflowExecutionFailed: 'child-workflow',
+  ChildWorkflowExecutionStarted: 'child-workflow',
+  ChildWorkflowExecutionTerminated: 'child-workflow',
+  ChildWorkflowExecutionTimedOut: 'child-workflow',
+  StartChildWorkflowExecutionFailed: 'child-workflow',
+  StartChildWorkflowExecutionInitiated: 'child-workflow',
+
+  SignalExternalWorkflowExecutionFailed: 'signal',
+  SignalExternalWorkflowExecutionInitiated: 'signal',
+
+  TimerCanceled: 'timer',
+  TimerFired: 'timer',
+  TimerStarted: 'timer',
+
+  WorkflowExecutionCanceled: 'workflow',
+  WorkflowExecutionCancelRequested: 'workflow',
+  WorkflowExecutionCompleted: 'workflow',
+  WorkflowExecutionContinuedAsNew: 'workflow',
+  WorkflowExecutionFailed: 'workflow',
+  WorkflowExecutionSignaled: 'workflow',
+  WorkflowExecutionStarted: 'workflow',
+  WorkflowExecutionTerminated: 'workflow',
+  WorkflowExecutionTimedOut: 'workflow',
+  WorkflowTaskCompleted: 'workflow',
+  WorkflowTaskFailed: 'workflow',
+  WorkflowTaskScheduled: 'workflow',
+  WorkflowTaskStarted: 'workflow',
+  WorkflowTaskTimedOut: 'workflow',
+  ExternalWorkflowExecutionCancelRequested: 'workflow',
+  ExternalWorkflowExecutionSignaled: 'workflow',
+  RequestCancelExternalWorkflowExecutionFailed: 'workflow',
+  RequestCancelExternalWorkflowExecutionInitiated: 'workflow',
+
+  MarkerRecorded: 'command',
+  UpsertWorkflowSearchAttributes: 'command',
+};
+
+const categories: EventTypeCategory[] = [
+  'activity',
+  'child-workflow',
+  'signal',
+  'timer',
+  'workflow',
+  'command',
+];
+
+export const eventTypeInCategory =
+  (category?: EventTypeCategory) =>
+  (event: HistoryEventWithId): boolean => {
+    if (!category) return true;
+    return eventTypeCategorizations[event.eventType] === category;
+  };
+
+export const isCategoryType = (value: string): value is EventTypeCategory => {
+  for (const category of categories) {
+    if (value === category) return true;
+  }
+  return false;
+};
diff --git a/src/lib/utilities/get-event-classification.ts b/src/lib/utilities/get-event-classification.ts
index a9b30064..c78e4a8d 100644
--- a/src/lib/utilities/get-event-classification.ts
+++ b/src/lib/utilities/get-event-classification.ts
@@ -18,7 +18,7 @@ export const eventClassifications = [
 ] as const;
 
 export const getEventClassification = (
-  event: HistoryEvent,
+  event: HistoryEventWithId,
 ): EventClassification => {
   const eventType = event.eventType.toString();
 
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte
index 55461bea..a43421b0 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte
@@ -20,13 +20,20 @@
 </script>
 
 <script lang="ts">
+  import { eventTypeInCategory } from '$lib/utilities/get-event-categorization';
+
   import PendingActivities from './_pending-activities.svelte';
   import Event from './_event.svelte';
+  import Option from '$lib/components/select/option.svelte';
+  import Select from '$lib/components/select/select.svelte';
 
   export let workflow: WorkflowExecution;
   export let events: HistoryEventWithId[];
 
   let pendingActivities = workflow?.pendingActivities;
+  let category: EventTypeCategory = null;
+
+  $: visibleEvents = events.filter(eventTypeInCategory(category));
 </script>
 
 <div class="execution-information px-6 py-6">
@@ -35,7 +42,18 @@
   </div>
   <section>
     <h3 class="text-lg mb-2 font-semibold">Event History</h3>
-    {#each events as event}
+    <div>
+      <Select bind:value={category}>
+        <Option value={null}>All</Option>
+        <Option value="activity">Activity</Option>
+        <Option value="command">Command</Option>
+        <Option value="signal">Signal</Option>
+        <Option value="timer">Timer</Option>
+        <Option value="child-workflow">Child Workflow</Option>
+        <Option value="workflow">Workflow</Option>
+      </Select>
+    </div>
+    {#each visibleEvents as event}
       <Event {event} />
     {/each}
   </section>


Test patch:
diff --git a/src/lib/utilities/get-event-categorization.test.ts b/src/lib/utilities/get-event-categorization.test.ts
new file mode 100644
index 00000000..b8a6f435
--- /dev/null
+++ b/src/lib/utilities/get-event-categorization.test.ts
@@ -0,0 +1,320 @@
+import {
+  eventTypeInCategory,
+  isCategoryType,
+} from './get-event-categorization';
+
+describe(eventTypeInCategory, () => {
+  it('should categorize a "ActivityTaskCanceled" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskCanceled' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskCancelRequested" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({
+        eventType: 'ActivityTaskCancelRequested',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskCompleted" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskCompleted' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskFailed" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskFailed' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskScheduled" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskScheduled' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskStarted" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskStarted' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ActivityTaskTimedOut" as a "activity"', () => {
+    expect(
+      eventTypeInCategory('activity')({ eventType: 'ActivityTaskTimedOut' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionCanceled" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionCanceled',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionCompleted" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionCompleted',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionFailed" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionFailed',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionStarted" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionStarted',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionTerminated" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionTerminated',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ChildWorkflowExecutionTimedOut" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'ChildWorkflowExecutionTimedOut',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "StartChildWorkflowExecutionFailed" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'StartChildWorkflowExecutionFailed',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "StartChildWorkflowExecutionInitiated" as a "child-workflow"', () => {
+    expect(
+      eventTypeInCategory('child-workflow')({
+        eventType: 'StartChildWorkflowExecutionInitiated',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "SignalExternalWorkflowExecutionFailed" as a "signal"', () => {
+    expect(
+      eventTypeInCategory('signal')({
+        eventType: 'SignalExternalWorkflowExecutionFailed',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "SignalExternalWorkflowExecutionInitiated" as a "signal"', () => {
+    expect(
+      eventTypeInCategory('signal')({
+        eventType: 'SignalExternalWorkflowExecutionInitiated',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "TimerCanceled" as a "timer"', () => {
+    expect(eventTypeInCategory('timer')({ eventType: 'TimerCanceled' })).toBe(
+      true,
+    );
+  });
+
+  it('should categorize a "TimerFired" as a "timer"', () => {
+    expect(eventTypeInCategory('timer')({ eventType: 'TimerFired' })).toBe(
+      true,
+    );
+  });
+
+  it('should categorize a "TimerStarted" as a "timer"', () => {
+    expect(eventTypeInCategory('timer')({ eventType: 'TimerStarted' })).toBe(
+      true,
+    );
+  });
+
+  it('should categorize a "WorkflowExecutionCanceled" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionCanceled',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionCancelRequested" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionCancelRequested',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionCompleted" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionCompleted',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionContinuedAsNew" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionContinuedAsNew',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionFailed" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowExecutionFailed' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionSignaled" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionSignaled',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionStarted" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionStarted',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionTerminated" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionTerminated',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowExecutionTimedOut" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'WorkflowExecutionTimedOut',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowTaskCompleted" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowTaskCompleted' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowTaskFailed" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowTaskFailed' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowTaskScheduled" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowTaskScheduled' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowTaskStarted" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowTaskStarted' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "WorkflowTaskTimedOut" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({ eventType: 'WorkflowTaskTimedOut' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ExternalWorkflowExecutionCancelRequested" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'ExternalWorkflowExecutionCancelRequested',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "ExternalWorkflowExecutionSignaled" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'ExternalWorkflowExecutionSignaled',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "RequestCancelExternalWorkflowExecutionFailed" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'RequestCancelExternalWorkflowExecutionFailed',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "RequestCancelExternalWorkflowExecutionInitiated" as a "workflow"', () => {
+    expect(
+      eventTypeInCategory('workflow')({
+        eventType: 'RequestCancelExternalWorkflowExecutionInitiated',
+      }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "MarkerRecorded" as a "command"', () => {
+    expect(
+      eventTypeInCategory('command')({ eventType: 'MarkerRecorded' }),
+    ).toBe(true);
+  });
+
+  it('should categorize a "UpsertWorkflowSearchAttributes" as a "command"', () => {
+    expect(
+      eventTypeInCategory('command')({
+        eventType: 'UpsertWorkflowSearchAttributes',
+      }),
+    ).toBe(true);
+  });
+});
+
+describe(isCategoryType, () => {
+  it('should return true for "activity"', () => {
+    expect(isCategoryType('activity')).toBeTruthy();
+  });
+
+  it('should return true for "child-workflow"', () => {
+    expect(isCategoryType('child-workflow')).toBeTruthy();
+  });
+
+  it('should return true for "signal"', () => {
+    expect(isCategoryType('signal')).toBeTruthy();
+  });
+
+  it('should return true for "timer"', () => {
+    expect(isCategoryType('timer')).toBeTruthy();
+  });
+
+  it('should return true for "workflow"', () => {
+    expect(isCategoryType('workflow')).toBeTruthy();
+  });
+
+  it('should return true for "command"', () => {
+    expect(isCategoryType('command')).toBeTruthy();
+  });
+});