Repository: temporalio/ui
Pull request #395 (2022-04-01 15:36:24)

Redesigned event views
## What was changed
New designs for the summary/full/compact views of event history. The default view of events is now compact to view groups of events.

<img width="1728" alt="Screen Shot 2022-04-01 at 10 33 31 AM" src="https://user-images.githubusercontent.com/7967403/161295887-e66e0635-f555-4007-a359-cf71f17b4413.png">
<img width="1728" alt="Screen Shot 2022-04-01 at 10 34 37 AM" src="https://user-images.githubusercontent.com/7967403/161295931-1bd5d7d7-38f3-4bed-92fa-9b03c0c417b4.png">
<img width="1727" alt="Screen Shot 2022-04-01 at 10 34 15 AM" src="https://user-images.githubusercontent.com/7967403/161295951-086cf39c-fa9f-4173-8698-030b388e071c.png">


<!-- Describe what has changed in this PR -->

## Why?
Based on new designs to make it easier to quickly navigate, filter and expand information about event history.

## Checklist

- [ ] Update collapsed row information based on Shawn's ranking documentation

2. How was this tested:
All unit/integration tests ran and passed, manual retest of workflows/importer with RainbowStatuses 


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/58/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">26</b>  &bull; <a href="https://dashboard.cypress.io/projects/g7xfxw/runs/58/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** | b1938aa0a2 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span> |
| **Started** | Apr 1, 2022 3:37 PM |
| **Ended** | Apr 1, 2022 3:38 PM |
| **Duration** | 01:02 <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/58/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)


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>f6d6121</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/b8bf84d5-f52c-4ce6-bbae-73e6a72629d9)


Patch:
diff --git a/src/global.d.ts b/src/global.d.ts
index 8095747a..de0fe4bd 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -72,3 +72,8 @@ type ClusterInformation = import('$types').GetClusterInfoResponse;
 type TimeFormat = 'UTC' | 'relative' | 'local';
 
 type SelectOptionValue = number | string | boolean;
+
+type OptionLabel = {
+  label: string;
+  option?: string;
+};
diff --git a/src/lib/components/button.svelte b/src/lib/components/button.svelte
index e3f59e85..8c889774 100644
--- a/src/lib/components/button.svelte
+++ b/src/lib/components/button.svelte
@@ -73,7 +73,7 @@
   }
 
   .primary:hover:enabled {
-    @apply bg-blue-800;
+    @apply bg-blue-700;
   }
 
   .secondary {
@@ -85,7 +85,7 @@
   }
 
   .secondary:hover:enabled {
-    @apply text-white bg-purple-300;
+    @apply text-white bg-gray-900;
   }
 
   .destroy {
diff --git a/src/lib/components/dropdown-menu.svelte b/src/lib/components/dropdown-menu.svelte
new file mode 100644
index 00000000..640ee84e
--- /dev/null
+++ b/src/lib/components/dropdown-menu.svelte
@@ -0,0 +1,115 @@
+<script lang="ts">
+  import Icon from 'svelte-fa';
+  import { faCheck } from '@fortawesome/free-solid-svg-icons';
+
+  import { page } from '$app/stores';
+  import { goto } from '$app/navigation';
+
+  import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
+  import { onMount } from 'svelte';
+  import { scale } from 'svelte/transition';
+  import IconButton from './icon-button.svelte';
+
+  import { updateQueryParameters } from '$lib/utilities/update-query-parameters';
+
+  export let value: string | undefined;
+  export let parameter: string;
+  export let options: OptionLabel[];
+
+  let show: boolean = false;
+  let menu: any = null;
+  $: _value = $page.url?.searchParams?.get(parameter) ?? undefined;
+
+  $: {
+    updateQueryParameters({
+      parameter,
+      value: _value,
+      url: $page.url,
+      goto,
+    }).then((v) => (value = v?.toString()));
+  }
+
+  const onOptionClick = (option: string) => {
+    _value = option;
+    show = false;
+  };
+
+  onMount(() => {
+    const handleOutsideClick = (event: Event) => {
+      if (show && !menu.contains(event.target)) {
+        show = false;
+      }
+    };
+
+    const handleEscape = (event: KeyboardEvent) => {
+      if (show && event?.key === 'Escape') {
+        show = false;
+      }
+    };
+
+    document.addEventListener('click', handleOutsideClick, false);
+    document.addEventListener('keyup', handleEscape, false);
+
+    return () => {
+      document.removeEventListener('click', handleOutsideClick, false);
+      document.removeEventListener('keyup', handleEscape, false);
+    };
+  });
+</script>
+
+<div class="relative inline mx-2" bind:this={menu}>
+  <IconButton
+    icon={faCaretDown}
+    on:click={() => (show = !show)}
+    classes="menu focus:outline-none focus:shadow-solid"
+  />
+  {#if show}
+    <div
+      in:scale={{ duration: 200, start: 0.65 }}
+      out:scale={{ duration: 100, start: 0.65 }}
+      class="origin-top-left absolute left-0 w-48 py-2 mt-1 bg-white
+        rounded shadow-md text-gray-900 z-50"
+    >
+      <div class="gap-4 block">
+        {#each options as { label, option } (option)}
+          <div
+            class="option"
+            class:active={value === option}
+            on:click={() => onOptionClick(option)}
+          >
+            <div class="check">
+              {#if value === option}
+                <Icon icon={faCheck} />
+              {/if}
+            </div>
+            <div class="label">
+              {label}
+            </div>
+          </div>
+        {/each}
+      </div>
+    </div>
+  {/if}
+  {#if _value}
+    <span
+      in:scale={{ duration: 200, start: 0.65 }}
+      out:scale={{ duration: 100, start: 0.65 }}
+      class="absolute top-0 right-3 w-2 h-2 bg-blue-200 rounded-full"
+    />
+  {/if}
+</div>
+
+<style lang="postcss">
+  .option {
+    @apply font-normal flex my-2;
+  }
+  .label {
+    @apply cursor-pointer;
+  }
+  .check {
+    @apply mx-4 w-4;
+  }
+  .active {
+    @apply text-blue-700;
+  }
+</style>
diff --git a/src/lib/components/event/event-category-menu.svelte b/src/lib/components/event/event-category-menu.svelte
new file mode 100644
index 00000000..b3de7141
--- /dev/null
+++ b/src/lib/components/event/event-category-menu.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+  import { page } from '$app/stores';
+  import DropdownMenu from '$lib/components/dropdown-menu.svelte';
+  import {
+    allEventTypeOptions,
+    compactEventTypeOptions,
+  } from '$lib/models/event-history/get-event-categorization';
+
+  export let compact: boolean = false;
+
+  let category = $page?.params?.category ?? undefined;
+  let options = compact ? compactEventTypeOptions : allEventTypeOptions;
+</script>
+
+<DropdownMenu parameter="category" value={category} {options} />
diff --git a/src/lib/components/event/event-classification.svelte b/src/lib/components/event/event-classification.svelte
deleted file mode 100644
index 857e23b6..00000000
--- a/src/lib/components/event/event-classification.svelte
+++ /dev/null
@@ -1,41 +0,0 @@
-<script lang="ts">
-  export let event: IterableEvent;
-</script>
-
-<span class="label {event.classification} font-semibold">
-  {event.name}
-</span>
-
-<style lang="postcss">
-  .label {
-    @apply px-2 text-gray-700 bg-gray-300;
-  }
-
-  .Open,
-  .New {
-    @apply text-indigo-700 bg-indigo-100;
-  }
-
-  .Started,
-  .Initiated {
-    @apply text-blue-700 bg-blue-100;
-  }
-
-  .Running,
-  .Completed,
-  .Fired {
-    @apply text-green-700 bg-green-100;
-  }
-
-  .CancelRequested,
-  .TimedOut,
-  .Signaled,
-  .Cancelled {
-    @apply text-yellow-700 bg-yellow-100;
-  }
-
-  .Failed,
-  .Terminated {
-    @apply text-red-700 bg-red-100;
-  }
-</style>
diff --git a/src/lib/components/event/event-compact-row.svelte b/src/lib/components/event/event-compact-row.svelte
new file mode 100644
index 00000000..e26a1935
--- /dev/null
+++ b/src/lib/components/event/event-compact-row.svelte
@@ -0,0 +1,75 @@
+<script lang="ts">
+  import EventSingleDetail from './event-single-detail.svelte';
+  import EventGroupDetails from './event-group-details.svelte';
+  import EventDetails from './event-details.svelte';
+
+  export let eventGroup: CompactEventGroups;
+
+  $: collapsed = true;
+  $: event = eventGroup.initialEvent;
+
+  const onLinkClick = () => {
+    collapsed = !collapsed;
+  };
+
+  const onEventClick = (id: string) => {
+    event = eventGroup.events.get(id);
+  };
+</script>
+
+<article class="row">
+  <div class="cell">
+    <div>
+      <span
+        class={collapsed ? 'link collapsed' : 'link expanded'}
+        on:click|stopPropagation={onLinkClick}>{eventGroup.name}</span
+      >
+      {#if !collapsed}
+        <div class="p-4">
+          <EventGroupDetails
+            {eventGroup}
+            eventId={event.id}
+            onClick={onEventClick}
+          />
+        </div>
+      {/if}
+    </div>
+  </div>
+  <div class="cell">
+    {#if collapsed}
+      <EventSingleDetail {eventGroup} />
+    {:else}
+      <EventDetails {event} />
+    {/if}
+  </div>
+</article>
+
+<style lang="postcss">
+  .cell {
+    @apply md:table-cell md:border-b-2 text-left p-2;
+  }
+
+  .row {
+    @apply no-underline p-2 text-sm border-b-2 items-center md:text-base md:table-row last-of-type:border-b-0;
+  }
+
+  .row:hover {
+    @apply bg-gray-50;
+  }
+
+  .link {
+    @apply text-gray-900 font-medium cursor-pointer;
+  }
+
+  .row:hover .collapsed {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+
+  .row:hover .expanded:hover {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+
+  .row:last-of-type .cell {
+    @apply border-b-0 first-of-type:rounded-bl-lg  last-of-type:rounded-br-lg;
+  }
+</style>
diff --git a/src/lib/components/event/event-compact-table.svelte b/src/lib/components/event/event-compact-table.svelte
new file mode 100644
index 00000000..4e015d3d
--- /dev/null
+++ b/src/lib/components/event/event-compact-table.svelte
@@ -0,0 +1,34 @@
+<script lang="ts">
+  import EventCategoryMenu from '$lib/components/event/event-category-menu.svelte';
+</script>
+
+<section class="compact-table">
+  <div class="table-header md:table-header-group">
+    <div class="md:table-row hidden">
+      <div class="table-header-cell w-3/12 rounded-tl-md">
+        Event Type<EventCategoryMenu compact />
+      </div>
+      <div class="table-header-cell w-1/2 rounded-tr-md">Event Details</div>
+    </div>
+  </div>
+  <div class="table-header md:hidden rounded-t-md">
+    <div class="table-header-cell">Compact<EventCategoryMenu compact /></div>
+  </div>
+  <div class="overflow-y-auto md:table-row-group">
+    <slot />
+  </div>
+</section>
+
+<style lang="postcss">
+  .compact-table {
+    @apply md:table border-gray-300 border-2 rounded-lg w-full;
+  }
+
+  .table-header {
+    @apply bg-gray-900 text-gray-100;
+  }
+
+  .table-header-cell {
+    @apply table-cell text-left p-3;
+  }
+</style>
diff --git a/src/lib/components/event/event-details.svelte b/src/lib/components/event/event-details.svelte
index bcd74e12..1ee0437d 100644
--- a/src/lib/components/event/event-details.svelte
+++ b/src/lib/components/event/event-details.svelte
@@ -1,26 +1,17 @@
 <script lang="ts">
   import { format } from '$lib/utilities/format-camel-case';
   import CodeBlock from '$lib/components/code-block.svelte';
-
+  import { shouldDisplayAttribute } from '$lib/utilities/get-single-attribute-for-event';
   export let event: HistoryEventWithId;
-
-  const shouldDisplay = (key: string, value: unknown): boolean => {
-    if (value === null) return false;
-    if (value === undefined) return false;
-    if (value === '') return false;
-    if (value === '0s') return false;
-    if (key === 'type') return false;
-    return true;
-  };
 </script>
 
 <section>
   {#each Object.entries(event.attributes) as [key, value] (key)}
-    {#if shouldDisplay(key, value)}
+    {#if shouldDisplayAttribute(key, value)}
       <article
-        class="flex w-full border-b-2 py-2 last:border-b-0 border-gray-200 first:pt-0 {$$props.class}"
+        class="flex py-2 last:border-b-0 border-gray-200 first:pt-0 {$$props.class}"
       >
-        <h4 class="w-96 flex-grow">{format(key)}</h4>
+        <p class="w-96 flex-grow">{format(key)}</p>
         <div class="flex-grow w-full">
           {#if typeof value === 'object'}
             <CodeBlock content={value} />
diff --git a/src/lib/components/event/event-empty-row.svelte b/src/lib/components/event/event-empty-row.svelte
new file mode 100644
index 00000000..a90b721f
--- /dev/null
+++ b/src/lib/components/event/event-empty-row.svelte
@@ -0,0 +1,18 @@
+<script lang="ts">
+  import EmptyState from '$lib/components/empty-state.svelte';
+</script>
+
+<article class="row">
+  <div class="cell">
+    <EmptyState
+      title="No Events Match"
+      content="There are no events that match your filters. Adjust your filters to see your events."
+    />
+  </div>
+</article>
+
+<style lang="postcss">
+  .row {
+    @apply absolute left-0 right-0 p-2 text-sm text-center border-2;
+  }
+</style>
diff --git a/src/lib/components/event/event-full.svelte b/src/lib/components/event/event-full.svelte
deleted file mode 100644
index 3e6b67cd..00000000
--- a/src/lib/components/event/event-full.svelte
+++ /dev/null
@@ -1,42 +0,0 @@
-<script lang="ts">
-  import { formatDate } from '$lib/utilities/format-date';
-
-  import Pagination from '$lib/components/pagination.svelte';
-  import EventDetails from '$lib/components/event/event-details.svelte';
-  import EventClassification from './event-classification.svelte';
-
-  export let events: HistoryEventWithId[];
-</script>
-
-<Pagination items={events} let:visibleItems>
-  <section class="border-2 border-gray-300 rounded-lg w-full mb-6">
-    <header class="flex table-header rounded-t-lg">
-      <h3 class="w-3/12">Workflow Events</h3>
-      <h3 class="w-2/12">Date & Time</h3>
-      <h3 class="w-7/12">Event Details</h3>
-    </header>
-    {#each visibleItems as event (event.id)}
-      <article class="table-row" id={event.id}>
-        <p class="w-3/12">
-          <a href="#{event.id}" class="mr-3">{event.id}</a><EventClassification
-            {event}
-          />
-        </p>
-        <p class="w-2/12">{formatDate(event.eventTime)}</p>
-        <div class="w-7/12 relative">
-          <EventDetails {event} />
-        </div>
-      </article>
-    {/each}
-  </section>
-</Pagination>
-
-<style lang="postcss">
-  .table-header {
-    @apply bg-gray-100 text-gray-800 font-semibold p-4 flex justify-between items-center border-b-2;
-  }
-
-  .table-row {
-    @apply flex border-b-2 border-gray-300 last-of-type:border-b-0 p-4;
-  }
-</style>
diff --git a/src/lib/components/event/event-group-details.svelte b/src/lib/components/event/event-group-details.svelte
index 96412d52..4d8d48b2 100644
--- a/src/lib/components/event/event-group-details.svelte
+++ b/src/lib/components/event/event-group-details.svelte
@@ -1,31 +1,33 @@
 <script lang="ts">
-  import { page } from '$app/stores';
-
-  import EventDetails from '$lib/components/event/event-details.svelte';
-  import Link from '$lib/components/link.svelte';
-
-  export let event: HistoryEventWithId;
   export let eventGroup: CompactEventGroup | null;
+  export let eventId: string;
+  export let onClick: (id: string) => void;
 </script>
 
 <section class="overflow-y-scroll max-h-full">
   {#if eventGroup}
     <nav class="flex flex-col mb-4">
-      <ul class="flex gap-4 w-full items-start">
+      <ul class="gap-4 w-full items-start">
         {#each [...eventGroup.events] as [id, eventInGroup] (id)}
-          <li>
-            <Link
-              sveltekit:noscroll
-              href={id + $page.url.search}
-              active={id === event.id}
+          <li on:click|preventDefault|stopPropagation={() => onClick(id)}>
+            <span class="event-type" class:active={id === eventId}
+              >{eventInGroup.eventType}</span
             >
-              {eventInGroup.eventType}
-            </Link>
           </li>
         {/each}
       </ul>
     </nav>
   {/if}
-
-  <EventDetails {event} />
 </section>
+
+<style lang="postcss">
+  li {
+    @apply my-2 cursor-pointer;
+  }
+  .event-type:hover {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+  .active {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+</style>
diff --git a/src/lib/components/event/event-history-import.svelte b/src/lib/components/event/event-history-import.svelte
index 3b3de147..9594d534 100644
--- a/src/lib/components/event/event-history-import.svelte
+++ b/src/lib/components/event/event-history-import.svelte
@@ -11,18 +11,21 @@
   // rawEvents is expected to be HistoryEvent[] | { events: HistoryEvent[] } but could be anything
   let rawEvents: any;
 
-  const onFileSelect = async (e) => {
-    const file = e.target.files[0];
+  const onFileSelect = async (e: Event) => {
+    const target = e.target as HTMLInputElement;
+    const file = target?.files?.[0];
     const reader = new FileReader();
-    reader.readAsText(file);
-    reader.onload = () => {
-      try {
-        const result = reader.result.toString();
-        rawEvents = JSON.parse(result);
-      } catch (e) {
-        notifications.add('error', 'Could not parse JSON');
-      }
-    };
+    if (file) {
+      reader.readAsText(file);
+      reader.onload = () => {
+        try {
+          const result = reader?.result?.toString() ?? '';
+          rawEvents = JSON.parse(result);
+        } catch (e) {
+          notifications.add('error', 'Could not parse JSON');
+        }
+      };
+    }
   };
 
   const onConfirm = async () => {
@@ -32,8 +35,7 @@
       );
       importEvents.set(events);
       importEventGroups.set(eventGroups);
-      const eventId = events[0]?.id ?? '1';
-      const path = routeForImport({ importType: 'events', eventId });
+      const path = routeForImport({ importType: 'events', view: 'summary' });
       goto(path);
     } catch (e) {
       notifications.add('error', 'Could not create event history from JSON');
@@ -42,11 +44,9 @@
 </script>
 
 <input
-  class="import-input block w-full border border-gray-200 rounded-md p-2"
+  class="import-input block border border-gray-200 rounded-md p-2"
   type="file"
   accept=".json"
   on:change={onFileSelect}
 />
-<Button classes="import-btn" icon={faFileImport} on:click={onConfirm}
-  >Import</Button
->
+<Button icon={faFileImport} on:click={onConfirm}>Import</Button>
diff --git a/src/lib/components/event/event-list-item.svelte b/src/lib/components/event/event-list-item.svelte
deleted file mode 100644
index 7ccfa1bb..00000000
--- a/src/lib/components/event/event-list-item.svelte
+++ /dev/null
@@ -1,60 +0,0 @@
-<script lang="ts">
-  import Icon from 'svelte-fa';
-  import { faCalendar } from '@fortawesome/free-solid-svg-icons';
-
-  import { page } from '$app/stores';
-  import { isEvent } from '$lib/models/event-history';
-  import { isEventGroup } from '$lib/models/group-events';
-  import EventClassification from './event-classification.svelte';
-
-  export let event: IterableEvent;
-
-  const isActive = (currentId: string): boolean => {
-    if (isEvent(event)) {
-      return event.id === currentId;
-    }
-
-    if (isEventGroup(event)) {
-      return event.eventIds.has(currentId);
-    }
-  };
-</script>
-
-<a
-  href={event.id + $page.url.search}
-  sveltekit:noscroll
-  id={event.id}
-  class="flex border-b-2 border-gray-300 w-full items-center hover:bg-gray-50"
-  class:active={isActive($page.params.eventId)}
->
-  <article class="flex gap-4 items-center p-4 w-full">
-    <p class="w-5 text-center text-gray-500">{event.id}</p>
-    <div class="w-full">
-      <h2 class="mb-2">
-        <EventClassification {event} />
-      </h2>
-      <p class="text-sm">
-        <Icon icon={faCalendar} class="inline" />
-        {event.timestamp}
-      </p>
-    </div>
-  </article>
-</a>
-
-<style lang="postcss">
-  .active {
-    background: theme('colors.blue[50]');
-    /* Creates a border without modifying the shape and size of the element. */
-    background-image: linear-gradient(
-      90deg,
-      theme('colors.blue[700]') 0%,
-      theme('colors.blue[700]') 1%,
-      theme('colors.blue[50]') 1%,
-      theme('colors.blue[50]') 1%
-    );
-  }
-
-  a:last-child {
-    @apply border-b-0;
-  }
-</style>
diff --git a/src/lib/components/event/event-single-detail.svelte b/src/lib/components/event/event-single-detail.svelte
new file mode 100644
index 00000000..7b8f9554
--- /dev/null
+++ b/src/lib/components/event/event-single-detail.svelte
@@ -0,0 +1,24 @@
+<script lang="ts">
+  import { getSingleAttributeForEvent } from '$lib/utilities/get-single-attribute-for-event';
+  import CodeBlock from '../code-block.svelte';
+
+  export let event: HistoryEventWithId | null = null;
+  export let eventGroup: CompactEventGroup | null = null;
+
+  let { key, value } = getSingleAttributeForEvent({ event, eventGroup });
+</script>
+
+<section>
+  <article
+    class="flex py-2 last:border-b-0 border-gray-200 first:pt-0 {$$props.class}"
+  >
+    <h4 class="w-96 flex-grow text-normal">{key}</h4>
+    <div class="flex-grow w-full">
+      {#if eventGroup}
+        <CodeBlock content={value} />
+      {:else}
+        <p><span class="bg-gray-300 text-gray-700 px-2">{value}</span></p>
+      {/if}
+    </div>
+  </article>
+</section>
diff --git a/src/lib/components/event/event-summary-row.svelte b/src/lib/components/event/event-summary-row.svelte
new file mode 100644
index 00000000..3f257fb0
--- /dev/null
+++ b/src/lib/components/event/event-summary-row.svelte
@@ -0,0 +1,64 @@
+<script lang="ts">
+  import { formatDate } from '$lib/utilities/format-date';
+
+  import EventDetails from './event-details.svelte';
+  import EventSingleDetail from './event-single-detail.svelte';
+
+  export let event: HistoryEventWithId;
+  export let expanded: boolean = false;
+
+  $: collapsed = expanded ? false : true;
+
+  const onLinkClick = () => {
+    if (!expanded) {
+      collapsed = !collapsed;
+    }
+  };
+</script>
+
+<article class="row" id={event.id}>
+  <div class="cell">
+    <span class="text-gray-500 text-normal mx-1">{event.id}</span>
+    <span
+      class="mx-2"
+      class:link={!expanded}
+      on:click|stopPropagation={onLinkClick}>{event.name}</span
+    >
+  </div>
+  <div class="cell links font-medium md:font-normal">
+    {formatDate(event?.eventTime)}
+  </div>
+  <div class="cell links">
+    {#if collapsed}
+      <EventSingleDetail {event} />
+    {:else}
+      <EventDetails {event} />
+    {/if}
+  </div>
+</article>
+
+<style lang="postcss">
+  .cell {
+    @apply md:table-cell md:border-b-2 text-left p-1 pt-2;
+  }
+
+  .row {
+    @apply no-underline p-1 text-sm border-b-2 items-center md:text-base md:table-row last-of-type:border-b-0;
+  }
+
+  .row:hover {
+    @apply bg-gray-50;
+  }
+
+  .link {
+    @apply text-gray-900 cursor-pointer;
+  }
+
+  .row:hover .link {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+
+  .row:last-of-type .cell {
+    @apply border-b-0 first-of-type:rounded-bl-lg  last-of-type:rounded-br-lg;
+  }
+</style>
diff --git a/src/lib/components/event/event-summary-table.svelte b/src/lib/components/event/event-summary-table.svelte
new file mode 100644
index 00000000..72ffb11f
--- /dev/null
+++ b/src/lib/components/event/event-summary-table.svelte
@@ -0,0 +1,35 @@
+<script lang="ts">
+  import EventCategoryMenu from '$lib/components/event/event-category-menu.svelte';
+</script>
+
+<section class="summary-table">
+  <div class="table-header-row md:table-header-group">
+    <div class="md:table-row hidden">
+      <div class="table-header w-3/12 rounded-tl-md">
+        Workflow Events<EventCategoryMenu />
+      </div>
+      <div class="table-header w-3/12">Date & Time</div>
+      <div class="table-header w-1/2 rounded-tr-md">Event Details</div>
+    </div>
+  </div>
+  <div class="table-header-row md:hidden rounded-t-md">
+    <div class="table-header">Summary<EventCategoryMenu /></div>
+  </div>
+  <div class="overflow-y-auto md:table-row-group">
+    <slot />
+  </div>
+</section>
+
+<style lang="postcss">
+  .summary-table {
+    @apply md:table border-gray-300 border-2 rounded-lg w-full;
+  }
+
+  .table-header-row {
+    @apply bg-gray-900 text-gray-100;
+  }
+
+  .table-header {
+    @apply table-cell text-left p-3;
+  }
+</style>
diff --git a/src/lib/components/event/event-summary.svelte b/src/lib/components/event/event-summary.svelte
deleted file mode 100644
index a50224e6..00000000
--- a/src/lib/components/event/event-summary.svelte
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts">
-  import { page } from '$app/stores';
-  import { afterNavigate } from '$app/navigation';
-
-  import EmptyState from '$lib/components/empty-state.svelte';
-  import Pagination from '$lib/components/pagination.svelte';
-  import FilterSelect from '$lib/components/select/filter-select.svelte';
-  import Option from '$lib/components/select/option.svelte';
-  import EventListItem from '$lib/components/event/event-list-item.svelte';
-
-  export let items: IterableEvents;
-  export let category: EventTypeCategory;
-
-  const startingIndex = items.findIndex(
-    ({ id }) => $page.params.eventId === id,
-  );
-
-  $: currentIndex = items.findIndex(({ id }) => $page.params.eventId === id);
-
-  afterNavigate(() => {
-    if (currentIndex > 0) {
-      document
-        .getElementById($page.params.eventId)
-        ?.scrollIntoView({ block: 'start' });
-    }
-  });
-</script>
-
-<Pagination {items} {startingIndex} let:visibleItems>
-  <section
-    class="flex flex-col border-2 border-gray-300 rounded-lg w-full mb-6"
-  >
-    <div class="flex w-full">
-      <header class="table-header border-r-2 rounded-tl-lg w-1/3">
-        <h3>Summary</h3>
-        <div class="flex items-center gap-4">
-          <FilterSelect parameter="category" bind:value={category}>
-            <Option value={undefined}>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>
-          </FilterSelect>
-        </div>
-      </header>
-      <header class="table-header rounded-tr-lg w-2/3">
-        <h3>Details</h3>
-      </header>
-    </div>
-    <div class="flex">
-      <div class="flex flex-col w-1/3 border-r-2 border-gray-300 rounded-bl-lg">
-        <div class="rounded-bl-lg overflow-y-scroll h-full">
-          {#each visibleItems as event (event.id)}
-            <EventListItem {event} />
-          {:else}
-            <div class="p-2">
-              <EmptyState
-                title="No Events Match"
-                content="There are no events that match your filters. Adjust your filters to see your events."
-              />
-            </div>
-          {/each}
-        </div>
-      </div>
-      <div class="flex flex-col w-2/3">
-        <div
-          class="overflow-y-scroll overflow-x-hidden rounded-br-lg px-4 w-full py-4 sticky top-0"
-        >
-          <slot />
-        </div>
-      </div>
-    </div>
-  </section>
-</Pagination>
-
-<style lang="postcss">
-  .table-header {
-    @apply bg-gray-100 text-gray-800 font-semibold p-4 flex justify-between items-center border-b-2;
-  }
-</style>
diff --git a/src/lib/components/event/view/event-compact.svelte b/src/lib/components/event/view/event-compact.svelte
new file mode 100644
index 00000000..36829d81
--- /dev/null
+++ b/src/lib/components/event/view/event-compact.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+  import Pagination from '$lib/components/pagination.svelte';
+  import LoadingRow from '$lib/components/loading-row.svelte';
+
+  import EventCompactTable from '$lib/components/event/event-compact-table.svelte';
+  import EventCompactRow from '$lib/components/event/event-compact-row.svelte';
+  import EventEmptyRow from '../event-empty-row.svelte';
+
+  export let items: IterableEvents;
+</script>
+
+{#await items}
+  <LoadingRow />
+{:then items}
+  <Pagination {items} floatId="event-view-toggle" let:visibleItems>
+    <EventCompactTable>
+      {#each visibleItems as eventGroup (eventGroup.id)}
+        <EventCompactRow {eventGroup} />
+      {/each}
+      {#if !items?.length}
+        <EventEmptyRow />
+      {/if}
+    </EventCompactTable>
+  </Pagination>
+{/await}
diff --git a/src/lib/components/event/view/event-full.svelte b/src/lib/components/event/view/event-full.svelte
new file mode 100644
index 00000000..c8e55496
--- /dev/null
+++ b/src/lib/components/event/view/event-full.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+  import LoadingRow from '$lib/components/loading-row.svelte';
+  import Pagination from '$lib/components/pagination.svelte';
+
+  import EventSummaryTable from '$lib/components/event/event-summary-table.svelte';
+  import EventSummaryRow from '$lib/components/event/event-summary-row.svelte';
+  import EventEmptyRow from '../event-empty-row.svelte';
+
+  export let items: HistoryEventWithId[];
+</script>
+
+{#await items}
+  <LoadingRow />
+{:then items}
+  <Pagination {items} floatId="event-view-toggle" let:visibleItems>
+    <EventSummaryTable>
+      {#each visibleItems as event (event.id)}
+        <EventSummaryRow {event} expanded />
+      {/each}
+      {#if !items?.length}
+        <EventEmptyRow />
+      {/if}
+    </EventSummaryTable>
+  </Pagination>
+{/await}
diff --git a/src/lib/components/event/view/event-summary.svelte b/src/lib/components/event/view/event-summary.svelte
new file mode 100644
index 00000000..f928dc3b
--- /dev/null
+++ b/src/lib/components/event/view/event-summary.svelte
@@ -0,0 +1,27 @@
+<script lang="ts">
+  import { page } from '$app/stores';
+
+  import LoadingRow from '$lib/components/loading-row.svelte';
+  import Pagination from '$lib/components/pagination.svelte';
+
+  import EventSummaryTable from '$lib/components/event/event-summary-table.svelte';
+  import EventSummaryRow from '$lib/components/event/event-summary-row.svelte';
+  import EventEmptyRow from '../event-empty-row.svelte';
+
+  export let items: HistoryEventWithId[];
+</script>
+
+{#await items}
+  <LoadingRow />
+{:then items}
+  <Pagination {items} floatId="event-view-toggle" let:visibleItems>
+    <EventSummaryTable>
+      {#each visibleItems as event (event.id)}
+        <EventSummaryRow {event} />
+      {/each}
+      {#if !items?.length}
+        <EventEmptyRow />
+      {/if}
+    </EventSummaryTable>
+  </Pagination>
+{/await}
diff --git a/src/lib/components/export-history.svelte b/src/lib/components/export-history.svelte
index 1467dcf8..6bde03d9 100644
--- a/src/lib/components/export-history.svelte
+++ b/src/lib/components/export-history.svelte
@@ -1,9 +1,9 @@
 <script lang="ts">
   import { page } from '$app/stores';
 
-  import ToggleButton from '$lib/components/toggle-button.svelte';
   import { fetchRawEvents } from '$lib/services/events-service';
   import { faDownload } from '@fortawesome/free-solid-svg-icons';
+  import Button from './button.svelte';
 
   const { workflow: workflowId, run: runId, namespace } = $page.params;
 
@@ -27,4 +27,6 @@
   };
 </script>
 
-<ToggleButton icon={faDownload} on:click={exportHistory} />
+<Button thin secondary icon={faDownload} on:click={exportHistory}
+  >Download</Button
+>
diff --git a/src/lib/components/icon-button.svelte b/src/lib/components/icon-button.svelte
new file mode 100644
index 00000000..be04f294
--- /dev/null
+++ b/src/lib/components/icon-button.svelte
@@ -0,0 +1,26 @@
+<script lang="ts">
+  import Icon from 'svelte-fa';
+
+  import type { IconDefinition } from '@fortawesome/free-solid-svg-icons';
+
+  export let icon: IconDefinition;
+  export let scale = 1;
+  export let classes: string = '';
+</script>
+
+<button class="icon-button" on:click>
+  {#if icon}
+    <div class="flex gap-2 items-center {classes}">
+      <Icon {icon} {scale} />
+      <slot />
+    </div>
+  {:else}
+    <slot />
+  {/if}
+</button>
+
+<style lang="postcss">
+  .icon-button {
+    @apply inline-block p-1 text-sm w-8 h-8;
+  }
+</style>
diff --git a/src/lib/components/loading-row.svelte b/src/lib/components/loading-row.svelte
new file mode 100644
index 00000000..1d15ae5e
--- /dev/null
+++ b/src/lib/components/loading-row.svelte
@@ -0,0 +1,5 @@
+<tr>
+  <td colspan="5" class="m-auto p-12 text-center font-extralight text-2xl">
+    Loading
+  </td>
+</tr>
diff --git a/src/lib/components/pagination.svelte b/src/lib/components/pagination.svelte
index 61a7d20d..698c9bfe 100644
--- a/src/lib/components/pagination.svelte
+++ b/src/lib/components/pagination.svelte
@@ -1,26 +1,51 @@
 <script lang="ts">
-  import Icon from 'svelte-fa';
-  import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons';
-
+  import { onMount } from 'svelte';
   import { page } from '$app/stores';
   import { pagination, perPageOptions } from '$lib/stores/pagination';
 
-  import FilterSelect from './select/filter-select.svelte';
+  import Icon from 'svelte-fa';
+  import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons';
 
   type T = $$Generic;
 
   export let key = 'per-page';
   export let items: T[];
-  export let startingIndex: string | number = null;
+  export let floatId: string | undefined = undefined;
+
+  import FilterSelect from './select/filter-select.svelte';
+
+  export let startingIndex: string | number = 0;
 
   $: perPage = $page.url.searchParams.get(key) || '100';
   $: store = pagination(items, perPage);
   $: store.adjustPageSize(perPage);
   $: store.jumpToIndex(startingIndex);
+
+  let screenWidth: number;
+  let width: number | undefined;
+  let height: number | undefined;
+
+  onMount(() => {
+    if (floatId) {
+      width = document.getElementById(floatId)?.clientWidth;
+    }
+  });
+
+  // If float width and nav height exist and screen is above lg breakpoint, float the nav
+  $: floatStyle =
+    width && height && screenWidth > 1024
+      ? `position: absolute; right: ${width + 20}px; top: -${height + 14}px`
+      : '';
 </script>
 
-<div class="flex flex-col gap-4">
-  <nav class="flex justify-end gap-8">
+<svelte:window bind:innerWidth={screenWidth} />
+
+<div class="pagination flex flex-col gap-4 relative mb-8">
+  <nav
+    style={floatStyle}
+    bind:clientHeight={height}
+    class="flex justify-end gap-8"
+  >
     <div class="flex gap-2 items-center justify-center">
       <p class="w-fit text-right">Per Page</p>
       <FilterSelect
diff --git a/src/lib/components/table-link.svelte b/src/lib/components/table-link.svelte
new file mode 100644
index 00000000..5a4a15af
--- /dev/null
+++ b/src/lib/components/table-link.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+  export let href: string;
+</script>
+
+<a {href} {...$$props} class="{$$props.class} table-link">
+  <slot />
+</a>
diff --git a/src/lib/components/toggle-button.svelte b/src/lib/components/toggle-button.svelte
index d75d8635..221d0c2c 100644
--- a/src/lib/components/toggle-button.svelte
+++ b/src/lib/components/toggle-button.svelte
@@ -15,7 +15,7 @@
 </script>
 
 <a
-  class="border-2 py-2 px-4 hover:text-white hover:bg-gray-600 flex items-center justify-center"
+  class="border-2 py-2 px-4 hover:text-white text-sm hover:bg-gray-600 flex items-center justify-center"
   class:rounded-lg={!group}
   class:active={$page.url.pathname.includes(base) || active}
   class:group
@@ -25,7 +25,7 @@
   {#if icon}
     <div class="flex gap-2 items-center">
       <Icon {icon} {scale} />
-      <slot />
+      <span class="hidden md:block"><slot /></span>
     </div>
   {:else}
     <slot />
diff --git a/src/lib/models/event-history/get-event-categorization.ts b/src/lib/models/event-history/get-event-categorization.ts
index 099c17fa..7cbd484c 100644
--- a/src/lib/models/event-history/get-event-categorization.ts
+++ b/src/lib/models/event-history/get-event-categorization.ts
@@ -58,6 +58,28 @@ const categories = [
   'command',
 ] as const;
 
+type EventTypeOption = {
+  label: string;
+  option: EventTypeCategory | undefined;
+};
+
+export const allEventTypeOptions: EventTypeOption[] = [
+  { label: 'All', option: undefined },
+  { label: 'Activity', option: 'activity' },
+  { label: 'Child Workflow', option: 'child-workflow' },
+  { label: 'Command', option: 'command' },
+  { label: 'Signal', option: 'signal' },
+  { label: 'Timer', option: 'timer' },
+  { label: 'Workflow', option: 'workflow' },
+];
+
+export const compactEventTypeOptions: EventTypeOption[] = [
+  { label: 'All', option: undefined },
+  { label: 'Activity', option: 'activity' },
+  { label: 'Signal', option: 'signal' },
+  { label: 'Timer', option: 'timer' },
+];
+
 export const getEventCategory = (eventType: EventType): EventTypeCategory => {
   return eventTypeCategorizations[eventType];
 };
diff --git a/src/lib/utilities/format-date.ts b/src/lib/utilities/format-date.ts
index d3dd17e3..6bd62e3f 100644
--- a/src/lib/utilities/format-date.ts
+++ b/src/lib/utilities/format-date.ts
@@ -8,7 +8,7 @@ type ValidTime = Parameters<typeof parseJSON>[0] | Timestamp;
 const pattern = 'yyyy-MM-dd z HH:mm:ss.SS';
 
 export function formatDate(
-  date: ValidTime,
+  date: ValidTime | undefined | null,
   timeFormat: TimeFormat = 'UTC',
 ): string {
   if (!date) return '';
diff --git a/src/lib/utilities/get-single-attribute-for-event.ts b/src/lib/utilities/get-single-attribute-for-event.ts
new file mode 100644
index 00000000..60ddfa26
--- /dev/null
+++ b/src/lib/utilities/get-single-attribute-for-event.ts
@@ -0,0 +1,45 @@
+export const shouldDisplayAttribute = (
+  key: string,
+  value: unknown,
+): boolean => {
+  if (value === null) return false;
+  if (value === undefined) return false;
+  if (value === '') return false;
+  if (value === '0s') return false;
+  if (key === 'type') return false;
+  return true;
+};
+
+const mapObjectToPropertValue = (key: string, value: any) => {
+  const firstKey = Object.keys(value)[0];
+  return { key: `${key}.${firstKey}`, value: value[firstKey] };
+};
+
+const getFirstDisplayAttribute = (attributes: any) => {
+  if (!attributes) return;
+  for (const [key, value] of Object.entries(attributes)) {
+    if (shouldDisplayAttribute(key, value)) {
+      if (typeof value === 'object') {
+        return mapObjectToPropertValue(key, value);
+      } else {
+        return { key, value: value.toString() };
+      }
+    }
+  }
+};
+
+export const getSingleAttributeForEvent = ({
+  event,
+  eventGroup,
+}: {
+  event: HistoryEventWithId | null;
+  eventGroup: CompactEventGroup | null;
+}): { key: string; value: string } => {
+  if (eventGroup) {
+    const attributes = eventGroup?.initialEvent?.attributes as any;
+    const input = attributes?.input ?? '';
+    return { key: 'input', value: input };
+  }
+
+  return getFirstDisplayAttribute(event?.attributes) ?? { key: '', value: '' };
+};
diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts
index 77ee6b35..d09aa534 100644
--- a/src/lib/utilities/route-for.ts
+++ b/src/lib/utilities/route-for.ts
@@ -130,15 +130,18 @@ export const routeForLoginPage = () => {
 };
 
 type ImportParameters = {
-  importType: string;
-  eventId?: string;
+  importType: 'events';
+  view?: EventView;
 };
 
 export const routeForImport = ({
   importType,
-  eventId,
+  view,
 }: ImportParameters): string => {
-  if (eventId) return `/import/${importType}/${eventId}`;
+  if (view) {
+    if (importType === 'events') return `/import/${importType}/history/${view}`;
+    return `/import/${importType}/${view}`;
+  }
   return `/import/${importType}`;
 };
 
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index e11d50c2..b54c3cc0 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -1,9 +1,6 @@
 <script context="module" lang="ts">
   import type { Load } from '@sveltejs/kit';
-  import type {
-    DescribeNamespaceResponse,
-    ListNamespacesResponse,
-  } from '$types';
+  import type { ListNamespacesResponse } from '$types';
 
   import '../app.postcss';
 
diff --git a/src/routes/import/events/[eventId]/index.svelte b/src/routes/import/events/[eventId]/index.svelte
deleted file mode 100644
index e359dce4..00000000
--- a/src/routes/import/events/[eventId]/index.svelte
+++ /dev/null
@@ -1,102 +0,0 @@
-<script context="module" lang="ts">
-  import type { Load } from '@sveltejs/kit';
-
-  export const load: Load = async function ({ params, url }) {
-    const { eventId } = params;
-
-    return {
-      props: {
-        eventId,
-      },
-    };
-  };
-</script>
-
-<script lang="ts">
-  import { page } from '$app/stores';
-  import { getGroupForEvent } from '$lib/models/group-events';
-  import {
-    faBars,
-    faCode,
-    faLayerGroup,
-    faTable,
-  } from '@fortawesome/free-solid-svg-icons';
-
-  import type { EventView } from '$lib/utilities/route-for';
-  import { importEvents, importEventGroups } from '$lib/stores/import-events';
-  import ImportHistory from '$lib/components/event/event-history-import.svelte';
-  import ToggleButton from '$lib/components/toggle-button.svelte';
-  import ToggleButtons from '$lib/components/toggle-buttons.svelte';
-  import EventSummary from '$lib/components/event/event-summary.svelte';
-  import EventFull from '$lib/components/event/event-full.svelte';
-  import EventGroupDetails from '$lib/components/event/event-group-details.svelte';
-  import CodeBlock from '$lib/components/code-block.svelte';
-
-  export let eventId: string;
-
-  let tab: EventView = 'summary';
-
-  $: category = $page.url.searchParams.get('category') as EventTypeCategory;
-  $: filteredEvents = $importEvents.filter((event) => {
-    if (category) return event.category === category;
-    return event;
-  });
-  $: filteredEventGroups = $importEventGroups.filter((event) => {
-    if (category) return event.category === category;
-    return event;
-  });
-
-  $: event = filteredEvents.find(
-    (event: HistoryEventWithId) => event.id === eventId,
-  );
-
-  $: eventGroup = event ? getGroupForEvent(event, $importEventGroups) : null;
-
-  $: activeView = (view: EventView) => tab === view;
-</script>
-
-<section class="flex flex-col gap-4">
-  <section id="event-history">
-    <nav class="relative flex gap-4 justify-between items-end pb-4 max-w-1/2">
-      <h3 class="text-lg font-medium">Event History</h3>
-      <div class="flex gap-4">
-        <ToggleButtons>
-          <ToggleButton
-            icon={faTable}
-            active={tab === 'summary'}
-            on:click={() => (tab = 'summary')}>Summary</ToggleButton
-          >
-          <ToggleButton
-            icon={faBars}
-            active={tab === 'full'}
-            on:click={() => (tab = 'full')}>Full</ToggleButton
-          >
-          <ToggleButton
-            icon={faLayerGroup}
-            active={tab === 'compact'}
-            on:click={() => (tab = 'compact')}>Compact</ToggleButton
-          >
-          <ToggleButton
-            icon={faCode}
-            active={tab === 'json'}
-            on:click={() => (tab = 'json')}>JSON</ToggleButton
-          >
-        </ToggleButtons>
-        <ImportHistory />
-      </div>
-    </nav>
-    {#if activeView('summary')}
-      <EventSummary items={filteredEvents} {category}>
-        {#if event}<EventGroupDetails {event} {eventGroup} />{/if}
-      </EventSummary>
-    {:else if activeView('full')}
-      <EventFull events={$importEvents} />
-    {:else if activeView('compact')}
-      <EventSummary items={filteredEventGroups} {category}>
-        {#if event}<EventGroupDetails {event} {eventGroup} />{/if}
-      </EventSummary>
-    {:else if activeView('json')}
-      <CodeBlock content={$importEvents} />
-    {/if}
-  </section>
-</section>
diff --git a/src/routes/import/events/history/__layout.svelte b/src/routes/import/events/history/__layout.svelte
new file mode 100644
index 00000000..b952bc94
--- /dev/null
+++ b/src/routes/import/events/history/__layout.svelte
@@ -0,0 +1,50 @@
+<script lang="ts">
+  import {
+    faBars,
+    faCode,
+    faLayerGroup,
+    faTable,
+  } from '@fortawesome/free-solid-svg-icons';
+
+  import { routeForImport } from '$lib/utilities/route-for';
+
+  import HistoryImport from '$lib/components/event/event-history-import.svelte';
+  import ToggleButton from '$lib/components/toggle-button.svelte';
+  import ToggleButtons from '$lib/components/toggle-buttons.svelte';
+</script>
+
+<section id="event-history">
+  <nav class="flex gap-4 justify-between items-end pb-4">
+    <h3 class="text-lg font-medium">Event History</h3>
+    <div class="flex gap-4">
+      <HistoryImport />
+    </div>
+  </nav>
+  <nav class="flex gap-4 justify-end items-end pb-4">
+    <div id="event-view-toggle" class="flex gap-4">
+      <ToggleButtons>
+        <ToggleButton
+          icon={faTable}
+          href={routeForImport({ importType: 'events', view: 'summary' })}
+          >Summary</ToggleButton
+        >
+        <ToggleButton
+          icon={faBars}
+          href={routeForImport({ importType: 'events', view: 'full' })}
+          >Full</ToggleButton
+        >
+        <ToggleButton
+          icon={faLayerGroup}
+          href={routeForImport({ importType: 'events', view: 'compact' })}
+          >Compact</ToggleButton
+        >
+        <ToggleButton
+          icon={faCode}
+          href={routeForImport({ importType: 'events', view: 'json' })}
+          >JSON</ToggleButton
+        >
+      </ToggleButtons>
+    </div>
+  </nav>
+  <slot />
+</section>
diff --git a/src/routes/import/events/history/compact/index.svelte b/src/routes/import/events/history/compact/index.svelte
new file mode 100644
index 00000000..331cc9d6
--- /dev/null
+++ b/src/routes/import/events/history/compact/index.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+  import { page } from '$app/stores';
+
+  import { importEventGroups } from '$lib/stores/import-events';
+  import CompactEvent from '$lib/components/event/view/event-compact.svelte';
+
+  $: category = $page.url.searchParams.get('category') as EventTypeCategory;
+  $: filteredEventGroups = $importEventGroups.filter(
+    (event: CompactEventGroups) => {
+      if (category) return event.category === category;
+      return event;
+    },
+  );
+</script>
+
+<CompactEvent items={filteredEventGroups} />
diff --git a/src/routes/import/events/history/full/index.svelte b/src/routes/import/events/history/full/index.svelte
new file mode 100644
index 00000000..200a4d93
--- /dev/null
+++ b/src/routes/import/events/history/full/index.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+  import { page } from '$app/stores';
+
+  import { importEvents } from '$lib/stores/import-events';
+  import FullEvent from '$lib/components/event/view/event-full.svelte';
+
+  $: category = $page.url.searchParams.get('category') as EventTypeCategory;
+  $: filteredEvents = $importEvents.filter((event) => {
+    if (category) return event.category === category;
+    return event;
+  });
+</script>
+
+<FullEvent items={filteredEvents} />
diff --git a/src/routes/import/events/history/index.svelte b/src/routes/import/events/history/index.svelte
new file mode 100644
index 00000000..419ae9d6
--- /dev/null
+++ b/src/routes/import/events/history/index.svelte
@@ -0,0 +1,9 @@
+<script context="module" lang="ts">
+  import type { Load } from '@sveltejs/kit';
+  import { routeForImport } from '$lib/utilities/route-for';
+
+  export const load: Load = async function ({ url }) {
+    const redirect = routeForImport({ importType: 'events', view: 'summary' });
+    return { status: 302, redirect };
+  };
+</script>
diff --git a/src/routes/import/events/history/json/index.svelte b/src/routes/import/events/history/json/index.svelte
new file mode 100644
index 00000000..f0ad46ea
--- /dev/null
+++ b/src/routes/import/events/history/json/index.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+  import CodeBlock from '$lib/components/code-block.svelte';
+
+  import { importEvents } from '$lib/stores/import-events';
+</script>
+
+<CodeBlock content={$importEvents} />
diff --git a/src/routes/import/events/history/summary/index.svelte b/src/routes/import/events/history/summary/index.svelte
new file mode 100644
index 00000000..b8c6bece
--- /dev/null
+++ b/src/routes/import/events/history/summary/index.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+  import { page } from '$app/stores';
+
+  import { importEvents } from '$lib/stores/import-events';
+  import EventSummary from '$lib/components/event/view/event-summary.svelte';
+
+  $: category = $page.url.searchParams.get('category') as EventTypeCategory;
+  $: filteredEvents = $importEvents.filter((event) => {
+    if (category) return event.category === category;
+    return event;
+  });
+</script>
+
+<EventSummary items={filteredEvents} />
diff --git a/src/routes/import/events/index.svelte b/src/routes/import/events/index.svelte
index 6c32483a..7d2a7306 100644
--- a/src/routes/import/events/index.svelte
+++ b/src/routes/import/events/index.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-  import ImportHistory from '$lib/components/event/event-history-import.svelte';
+  import HistoryImport from '$lib/components/event/event-history-import.svelte';
   import CodeBlock from '$lib/components/code-block.svelte';
   import Link from '$lib/components/link.svelte';
 
@@ -13,7 +13,7 @@
     <nav class="flex gap-4 justify-between items-end pb-4">
       <h3 class="text-lg font-medium">Event History Import</h3>
       <div class="flex gap-4">
-        <ImportHistory />
+        <HistoryImport />
       </div>
     </nav>
   </section>
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/_header.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/_header.svelte
index 4b252368..3afe1d44 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/_header.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/_header.svelte
@@ -13,11 +13,13 @@
   } from '$lib/utilities/route-for';
   import { formatDate } from '$lib/utilities/format-date';
 
+  import type { GetPollersResponse } from '$lib/services/pollers-service';
+  import Link from '$lib/components/link.svelte';
+
   import WorkflowStatus from '$lib/components/workflow-status.svelte';
   import TerminateWorkflow from '$lib/components/terminate-workflow.svelte';
+  import ExportHistory from '$lib/components/export-history.svelte';
   import Tab from '$lib/components/tab.svelte';
-  import type { GetPollersResponse } from '$lib/services/pollers-service';
-  import Link from '$lib/components/link.svelte';
 
   export let namespace: string;
   export let workflow: WorkflowExecution;
@@ -48,7 +50,10 @@
         <span class="font-medium">{workflow.id}</span>
         <WorkflowStatus status={workflow?.status} />
       </h1>
-      <TerminateWorkflow {workflow} {namespace} />
+      <div class="flex justify-end items-center gap-4">
+        <ExportHistory />
+        <TerminateWorkflow {workflow} {namespace} />
+      </div>
     </div>
     <nav class="flex gap-6 mb-6">
       <Tab
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/[eventId].svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/[eventId].svelte
deleted file mode 100644
index 51555559..00000000
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/[eventId].svelte
+++ /dev/null
@@ -1,62 +0,0 @@
-<script lang="ts" context="module">
-  import type { Load } from '@sveltejs/kit';
-
-  import { getGroupForEvent } from '$lib/models/group-events';
-  import { routeForEventHistory } from '$lib/utilities/route-for';
-  import type { EventHistoryParameters } from '$lib/utilities/route-for';
-
-  /**
-   * Returns `true` if we're trying to view an event that has been filtered
-   * out from the sidebar.
-   *
-   * @param event The event as determined by the route
-   * @param eventGroup The event group that its a member of
-   * @param stuff SvelteKit's `stuff` object
-   * @param params Route parameters
-   */
-  const shouldRedirect = (
-    event: HistoryEventWithId,
-    eventGroup: CompactEventGroup,
-    { matchingEvents }: Partial<App.Stuff>,
-    { view }: Record<string, string>,
-  ): boolean => {
-    if (!matchingEvents.includes(event)) return true;
-    if (view === 'compact' && !eventGroup) return true;
-    return false;
-  };
-
-  export const load: Load = async function ({ params, stuff, url }) {
-    const { eventId } = params;
-    const { events, eventGroups } = stuff;
-
-    const event: HistoryEventWithId = events.find(
-      (event: HistoryEventWithId) => event.id === eventId,
-    );
-
-    if (!event) return { status: 404 };
-
-    const eventGroup: CompactEventGroup = getGroupForEvent(event, eventGroups);
-
-    if (shouldRedirect(event, eventGroup, stuff, params)) {
-      url.pathname = routeForEventHistory(params as EventHistoryParameters);
-
-      return {
-        status: 302,
-        redirect: String(url),
-      };
-    }
-
-    return {
-      props: { event, eventGroup },
-    };
-  };
-</script>
-
-<script lang="ts">
-  import EventGroupDetails from '$lib/components/event/event-group-details.svelte';
-
-  export let event: HistoryEventWithId;
-  export let eventGroup: CompactEventGroup;
-</script>
-
-<EventGroupDetails {event} {eventGroup} />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/__layout.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/__layout.svelte
deleted file mode 100644
index 3a8b907b..00000000
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/__layout.svelte
+++ /dev/null
@@ -1,40 +0,0 @@
-<script context="module" lang="ts">
-  import type { Load } from '@sveltejs/kit';
-
-  import { getEventsInCategory } from '$lib/models/event-history/get-event-categorization';
-
-  export const load: Load = async function ({ stuff, url, params }) {
-    const category = url.searchParams.get('category');
-    const validViews = ['summary', 'compact', 'json'];
-
-    const events = getEventsInCategory(stuff.events, category);
-    const eventGroups = getEventsInCategory(stuff.eventGroups, category);
-
-    let items: HistoryEventWithId[] | CompactEventGroups;
-
-    if (params.view === 'summary') items = events;
-    if (params.view === 'compact') items = eventGroups;
-
-    if (!validViews.includes(params.view)) return { status: 404 };
-
-    return {
-      props: {
-        items,
-        category,
-      },
-      stuff: {
-        matchingEvents: events,
-        matchingEventGroups: eventGroups,
-      },
-    };
-  };
-</script>
-
-<script lang="ts">
-  import EventSummary from '$lib/components/event/event-summary.svelte';
-
-  export let items: IterableEvents;
-  export let category: EventTypeCategory;
-</script>
-
-<EventSummary {items} {category}><slot /></EventSummary>
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/index.svelte
deleted file mode 100644
index c4fc127d..00000000
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/[view]/index.svelte
+++ /dev/null
@@ -1,25 +0,0 @@
-<script context="module" lang="ts">
-  import type { Load } from '@sveltejs/kit';
-
-  export const load: Load = async function ({ url, params, stuff }) {
-    const { matchingEvents, matchingEventGroups } = stuff;
-
-    let items: IterableEvents;
-
-    if (params.view === 'summary') items = matchingEvents;
-    if (params.view === 'compact') items = matchingEventGroups;
-
-    const [first] = items;
-
-    if (matchingEvents.length && first?.id) {
-      url.pathname = `${url.pathname}/${first.id}`;
-
-      return {
-        status: 302,
-        redirect: String(url),
-      };
-    }
-
-    return {};
-  };
-</script>
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte
index ac4dc291..8b135e80 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte
@@ -1,9 +1,6 @@
 <script context="module" lang="ts">
   import type { Load } from '@sveltejs/kit';
-  import {
-    EventView,
-    routeForEventHistoryItem,
-  } from '$lib/utilities/route-for';
+  import type { EventView } from '$lib/utilities/route-for';
 
   import { fetchEvents } from '$lib/services/events-service';
 
@@ -41,11 +38,9 @@
     faTable,
   } from '@fortawesome/free-solid-svg-icons';
 
-  import { page } from '$app/stores';
   import { routeForEventHistory } from '$lib/utilities/route-for';
   import { getWorkflowStartedAndCompletedEvents } from '$lib/utilities/get-started-and-completed-events';
 
-  import ExportHistory from '$lib/components/export-history.svelte';
   import ToggleButton from '$lib/components/toggle-button.svelte';
   import ToggleButtons from '$lib/components/toggle-buttons.svelte';
   import CodeBlock from '$lib/components/code-block.svelte';
@@ -73,15 +68,14 @@
   <PendingActivties />
   <section id="event-history">
     <nav class="flex gap-4 justify-between items-end pb-4">
-      <h3 class="text-lg font-medium">Event History</h3>
-      <div class="flex gap-4">
+      <h3 class="text-lg font-medium">Recent Events</h3>
+      <div id="event-view-toggle" class="flex gap-4">
         <ToggleButtons>
           <ToggleButton
             icon={faTable}
             base={routeForEventHistory(routeParameters('summary'))}
-            href={routeForEventHistoryItem(
-              routeParameters('summary', $page.params.eventId || '1'),
-            )}>Summary</ToggleButton
+            href={routeForEventHistory(routeParameters('summary'))}
+            >Summary</ToggleButton
           >
           <ToggleButton
             icon={faBars}
@@ -99,7 +93,6 @@
             >JSON</ToggleButton
           >
         </ToggleButtons>
-        <ExportHistory />
       </div>
     </nav>
     <slot />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte
new file mode 100644
index 00000000..32725007
--- /dev/null
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte
@@ -0,0 +1,30 @@
+<script context="module" lang="ts">
+  import type { Load } from '@sveltejs/kit';
+
+  import { getEventsInCategory } from '$lib/models/event-history/get-event-categorization';
+
+  export const load: Load = async function ({ stuff, url }) {
+    const category = url.searchParams.get('category');
+
+    const eventGroups = getEventsInCategory(stuff.eventGroups, category);
+
+    let items: CompactEventGroups = eventGroups;
+
+    return {
+      props: {
+        items,
+      },
+      stuff: {
+        matchingEventGroups: eventGroups,
+      },
+    };
+  };
+</script>
+
+<script lang="ts">
+  import CompactEvent from '$lib/components/event/view/event-compact.svelte';
+
+  export let items: IterableEvents;
+</script>
+
+<CompactEvent {items} />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full.svelte
deleted file mode 100644
index 8e751392..00000000
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full.svelte
+++ /dev/null
@@ -1,21 +0,0 @@
-<script context="module" lang="ts">
-  import type { Load } from '@sveltejs/kit';
-
-  export const load: Load = async function ({ stuff }) {
-    const { events } = stuff;
-
-    return {
-      props: {
-        events,
-      },
-    };
-  };
-</script>
-
-<script lang="ts">
-  import FullEvent from '$lib/components/event/event-full.svelte';
-
-  export let events: HistoryEventWithId[];
-</script>
-
-<FullEvent {events} />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full/index.svelte
new file mode 100644
index 00000000..d7524e45
--- /dev/null
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/full/index.svelte
@@ -0,0 +1,25 @@
+<script context="module" lang="ts">
+  import type { Load } from '@sveltejs/kit';
+
+  import { getEventsInCategory } from '$lib/models/event-history/get-event-categorization';
+
+  export const load: Load = async function ({ stuff, url }) {
+    const category = url.searchParams.get('category');
+
+    const items = getEventsInCategory(stuff.events, category);
+
+    return {
+      props: {
+        items,
+      },
+    };
+  };
+</script>
+
+<script lang="ts">
+  import FullEvent from '$lib/components/event/view/event-full.svelte';
+
+  export let items: HistoryEventWithId[];
+</script>
+
+<FullEvent {items} />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte
index f1e4931e..9d860c5d 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte
@@ -2,9 +2,6 @@
   import type { Load } from '@sveltejs/kit';
 
   export const load: Load = async function ({ url }) {
-    return {
-      status: 302,
-      redirect: `${url.pathname}/summary`,
-    };
+    return { status: 302, redirect: `${url.pathname}/compact` };
   };
 </script>
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/index.svelte
similarity index 100%
rename from src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json.svelte
rename to src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/index.svelte
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/summary/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/summary/index.svelte
new file mode 100644
index 00000000..ecb680a0
--- /dev/null
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/summary/index.svelte
@@ -0,0 +1,30 @@
+<script context="module" lang="ts">
+  import type { Load } from '@sveltejs/kit';
+
+  import { getEventsInCategory } from '$lib/models/event-history/get-event-categorization';
+
+  export const load: Load = async function ({ stuff, url }) {
+    const category = url.searchParams.get('category');
+
+    const events = getEventsInCategory(stuff.events, category);
+
+    let items: HistoryEventWithId[] = events;
+
+    return {
+      props: {
+        items,
+      },
+      stuff: {
+        matchingEvents: events,
+      },
+    };
+  };
+</script>
+
+<script lang="ts">
+  import EventSummary from '$lib/components/event/view/event-summary.svelte';
+
+  export let items: HistoryEventWithId[];
+</script>
+
+<EventSummary {items} />
diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte
index a4225c84..d635dd95 100644
--- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte
@@ -3,7 +3,7 @@
 
   import type { GetPollersResponse } from '$lib/services/pollers-service';
 
-  export const load: Load = async function ({ params, stuff }) {
+  export const load: Load = async function ({ stuff }) {
     const { workflow, workers } = stuff;
     const { taskQueue } = workflow as WorkflowExecution;
 
diff --git a/src/routes/namespaces/[namespace]/workflows/_workflows-summary-row.svelte b/src/routes/namespaces/[namespace]/workflows/_workflows-summary-row.svelte
index f64211f4..35871337 100644
--- a/src/routes/namespaces/[namespace]/workflows/_workflows-summary-row.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/_workflows-summary-row.svelte
@@ -4,7 +4,7 @@
   import WorkflowStatus from '$lib/components/workflow-status.svelte';
   import { routeForWorkflow } from '$lib/utilities/route-for';
 
-  import Link from '$lib/components/link.svelte';
+  import TableLink from '$lib/components/table-link.svelte';
 
   export let namespace: string;
   export let workflow: WorkflowExecution;
@@ -24,20 +24,22 @@
     </div>
   </div>
   <div class="cell links font-medium md:font-normal">
-    <Link {href}>{workflow.id}</Link>
+    <p>
+      <TableLink {href}>{workflow.id}</TableLink>
+    </p>
   </div>
-  <div class="cell links">
-    <h3>
-      <Link {href}>{workflow.name}</Link>
-    </h3>
+  <div class="cell links font-medium md:font-normal">
+    <p>
+      <TableLink {href}>{workflow.name}</TableLink>
+    </p>
   </div>
-  <div class="inline-block  cell">
+  <div class="inline-block cell font-normal">
     <p>
       {formatDate(workflow.startTime, timeFormat)}
     </p>
   </div>
   <span class=" md:hidden"> - </span>
-  <div class="inline-block cell">
+  <div class="inline-block cell font-normal">
     <p>
       {formatDate(workflow.endTime, timeFormat)}
     </p>
@@ -46,7 +48,7 @@
 
 <style lang="postcss">
   .row {
-    @apply no-underline p-2 text-sm border-b-2 items-center md:text-base md:table-row;
+    @apply no-underline p-2 text-sm border-b-2 items-center md:text-base md:table-row last-of-type:border-b-0;
   }
 
   .cell {
@@ -56,4 +58,12 @@
   .row:hover {
     @apply bg-gray-50;
   }
+
+  .row:hover :global(.table-link) {
+    @apply text-blue-700 border-b-2 border-blue-700;
+  }
+
+  .row:last-of-type .cell {
+    @apply border-b-0 first-of-type:rounded-bl-lg  last-of-type:rounded-br-lg;
+  }
 </style>
diff --git a/src/routes/namespaces/[namespace]/workflows/_workflows-summary-table.svelte b/src/routes/namespaces/[namespace]/workflows/_workflows-summary-table.svelte
index e322492f..6bc0f611 100644
--- a/src/routes/namespaces/[namespace]/workflows/_workflows-summary-table.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/_workflows-summary-table.svelte
@@ -1,15 +1,15 @@
 <section class="workflow-table">
-  <div class="md:table-header-group bg-gray-100 rounded-t-lg">
+  <div class="table-header-row md:table-header-group">
     <div class="md:table-row hidden">
-      <div class="table-header rounded-tl-lg">Status</div>
+      <div class="table-header rounded-tl-md">Status</div>
       <div class="table-header">Workflow ID</div>
       <div class="table-header">Type</div>
-      <div class="table-header">Started</div>
-      <div class="table-header rounded-tr-lg">End</div>
+      <div class="table-header">Start</div>
+      <div class="table-header rounded-tr-md">End</div>
     </div>
   </div>
-  <div class="md:hidden">
-    <div class="bg-gray-100 p-2 border-b rounded-t-lg">Summary</div>
+  <div class="table-header-row md:hidden">
+    <div class="p-2 border-b rounded-t-md">Summary</div>
   </div>
   <div class="overflow-y-auto md:table-row-group">
     <slot />
@@ -21,6 +21,10 @@
     @apply md:table border-gray-300 border-2 rounded-lg w-full mb-6;
   }
 
+  .table-header-row {
+    @apply bg-gray-900 text-white rounded-t-lg;
+  }
+
   .table-header {
     @apply table-cell text-left p-2;
   }
diff --git a/src/routes/namespaces/[namespace]/workflows/index.svelte b/src/routes/namespaces/[namespace]/workflows/index.svelte
index de387126..5b7e28b1 100644
--- a/src/routes/namespaces/[namespace]/workflows/index.svelte
+++ b/src/routes/namespaces/[namespace]/workflows/index.svelte
@@ -39,9 +39,9 @@
   import WorkflowsSummaryRow from './_workflows-summary-row.svelte';
   import WorkflowFilters from './_workflow-filters.svelte';
   import EmptyState from '$lib/components/empty-state.svelte';
-  import WorkflowsLoadingState from './_workflows-loading.svelte';
   import Pagination from '$lib/components/pagination.svelte';
   import Badge from '$lib/components/badge.svelte';
+  import LoadingRow from '$lib/components/loading-row.svelte';
 
   export let namespace: string;
   export let workflows: CombinedWorkflowExecutionsResponse;
@@ -57,7 +57,7 @@
 <h2 class="text-2xl">Workflows <Badge type="beta">Beta</Badge></h2>
 <WorkflowFilters bind:timeFormat />
 {#await workflows}
-  <WorkflowsLoadingState />
+  <LoadingRow />
 {:then { workflows }}
   {#if workflows.length}
     <Pagination items={workflows} let:visibleItems>


Test patch:
diff --git a/src/lib/utilities/get-single-attribute-for-event.test.ts b/src/lib/utilities/get-single-attribute-for-event.test.ts
new file mode 100644
index 00000000..105a2087
--- /dev/null
+++ b/src/lib/utilities/get-single-attribute-for-event.test.ts
@@ -0,0 +1,159 @@
+import { getSingleAttributeForEvent } from './get-single-attribute-for-event';
+
+describe(getSingleAttributeForEvent, () => {
+  const workflowEvent = {
+    eventId: '1',
+    eventTime: '2022-03-14T17:44:14.996241458Z',
+    eventType: 'WorkflowExecutionStarted',
+    version: '0',
+    taskId: '2097152',
+    workflowExecutionStartedEventAttributes: {
+      workflowType: {
+        name: 'RainbowStatusesWorkflow',
+      },
+      parentWorkflowNamespace: '',
+      parentWorkflowExecution: null,
+      parentInitiatedEventId: '0',
+      taskQueue: {
+        name: 'rainbow-statuses',
+        kind: 'Normal',
+      },
+      input: {
+        payloads: null,
+      },
+      workflowExecutionTimeout: '0s',
+      workflowRunTimeout: '0s',
+      workflowTaskTimeout: '10s',
+      continuedExecutionRunId: '',
+      initiator: 'Unspecified',
+      continuedFailure: null,
+      lastCompletionResult: null,
+      originalExecutionRunId: 'b28e27e5-364f-4807-a8e2-b964f2c8aeb7',
+      identity: '47339@MacBook-Pro@',
+      firstExecutionRunId: 'b28e27e5-364f-4807-a8e2-b964f2c8aeb7',
+      retryPolicy: null,
+      attempt: 1,
+      workflowExecutionExpirationTime: null,
+      cronSchedule: '',
+      firstWorkflowTaskBackoff: '0s',
+      memo: null,
+      searchAttributes: {
+        indexedFields: {
+          CustomBoolField: true,
+          CustomDatetimeField: '2022-03-14T17:44:14.999501Z',
+          CustomDoubleField: 0,
+          CustomIntField: 0,
+          CustomKeywordField: 'rainbow-statuses-61404c',
+          CustomStringField: 'rainbow statuses 61404c Running',
+        },
+      },
+      prevAutoResetPoints: null,
+      header: {
+        fields: {},
+      },
+    },
+    id: '1',
+    attributes: {
+      type: 'workflowExecutionStartedEventAttributes',
+      workflowType: {
+        name: 'RainbowStatusesWorkflow',
+      },
+      parentWorkflowNamespace: '',
+      parentWorkflowExecution: null,
+      taskQueue: {
+        name: 'rainbow-statuses',
+        kind: 'Normal',
+      },
+      parentInitiatedEventId: '0',
+      input: {
+        payloads: [
+          {
+            Hey: 'from Mars',
+            At: '2022-04-04T11:50:28.151785-05:00',
+          },
+        ],
+      },
+      workflowExecutionTimeout: '0s',
+      workflowRunTimeout: '0s',
+      workflowTaskTimeout: '10s',
+      continuedExecutionRunId: '',
+      initiator: 'Unspecified',
+      continuedFailure: null,
+      lastCompletionResult: null,
+      originalExecutionRunId: 'b28e27e5-364f-4807-a8e2-b964f2c8aeb7',
+      identity: '47339@MacBook-Pro@',
+      firstExecutionRunId: 'b28e27e5-364f-4807-a8e2-b964f2c8aeb7',
+      retryPolicy: null,
+      attempt: 1,
+      workflowExecutionExpirationTime: null,
+      cronSchedule: '',
+      firstWorkflowTaskBackoff: '0s',
+      memo: null,
+      searchAttributes: {
+        indexedFields: {
+          CustomBoolField: true,
+          CustomDatetimeField: '2022-03-14T17:44:14.999501Z',
+          CustomDoubleField: 0,
+          CustomIntField: 0,
+          CustomKeywordField: 'rainbow-statuses-61404c',
+          CustomStringField: 'rainbow statuses 61404c Running',
+        },
+      },
+      prevAutoResetPoints: null,
+      header: {
+        fields: {},
+      },
+    },
+  };
+
+  const workflowEventGroup = {
+    initialEvent: workflowEvent,
+  };
+
+  it('should return "workflowType.name" if the workflow type exists', () => {
+    expect(
+      getSingleAttributeForEvent({ event: workflowEvent, eventGroup: null }),
+    ).toStrictEqual({
+      key: 'workflowType.name',
+      value: 'RainbowStatusesWorkflow',
+    });
+  });
+
+  it('should return "taskqueue.name" if the workflow type does not exists', () => {
+    const event = { ...workflowEvent };
+    event.attributes.workflowType = null;
+    expect(
+      getSingleAttributeForEvent({ event, eventGroup: null }),
+    ).toStrictEqual({ key: 'taskQueue.name', value: 'rainbow-statuses' });
+  });
+
+  it('should return "parentInitiatedEventId" if the workflow type and task queue does not exists', () => {
+    const event = { ...workflowEvent };
+    event.attributes.workflowType = null;
+    event.attributes.taskQueue = null;
+    expect(
+      getSingleAttributeForEvent({ event, eventGroup: null }),
+    ).toStrictEqual({ key: 'parentInitiatedEventId', value: '0' });
+  });
+
+  it('should return empty key value object if none of the attributes display', () => {
+    expect(
+      getSingleAttributeForEvent({ event: null, eventGroup: null }),
+    ).toStrictEqual({ key: '', value: '' });
+  });
+
+  it('should return "input" if passed an event group', () => {
+    const event = { ...workflowEvent };
+    const value = {
+      payloads: [
+        {
+          Hey: 'from Mars',
+          At: '2022-04-04T11:50:28.151785-05:00',
+        },
+      ],
+    };
+    expect(
+      getSingleAttributeForEvent({ event, eventGroup: workflowEventGroup }),
+    ).toStrictEqual({ key: 'input', value });
+  });
+});
diff --git a/src/lib/utilities/route-for.test.ts b/src/lib/utilities/route-for.test.ts
index 40943a32..35ee95d0 100644
--- a/src/lib/utilities/route-for.test.ts
+++ b/src/lib/utilities/route-for.test.ts
@@ -6,6 +6,7 @@ import {
   routeForWorkflow,
   routeForWorkflowQuery,
   routeForWorkflows,
+  routeForImport,
 } from './route-for';
 
 describe('routeFor', () => {
@@ -81,6 +82,22 @@ describe('routeFor', () => {
   });
 });
 
+describe('routeFor import ', () => {
+  it('should default route to "import/events" for import', () => {
+    const path = routeForImport({
+      importType: 'events',
+    });
+    expect(path).toBe('/import/events');
+  });
+  it('should route to "import/events/history/compact" for import', () => {
+    const path = routeForImport({
+      importType: 'events',
+      view: 'compact',
+    });
+    expect(path).toBe('/import/events/history/compact');
+  });
+});
+
 describe('routeFor sso authentication ', () => {
   it('Options added through settings should be passed in the url', () => {
     const settings = {