Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/lib/components/event/event-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
{@const href = getEventLinkHref(link)}
{@const value = href.split('workflows/')?.[1] || href}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm text-secondary/80">
<p class="min-w-56 text-sm font-medium text-secondary">
{translate('nexus.link')}
</p>
<Copyable
Expand All @@ -141,7 +141,7 @@
{#snippet eventNamespaceLink(link: ELink)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • ⚠️ Type 'string | null | undefined' is not assignable to type 'string'.

{@const href = routeForNamespace({ namespace: link.workflowEvent.namespace })}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm text-secondary/80">
<p class="min-w-56 text-sm font-medium text-secondary">
{translate('nexus.link-namespace')}
</p>
<Copyable
Expand All @@ -157,7 +157,7 @@
{/snippet}

{#snippet eventLinks(links: ELink[])}
{#each links as link}

Check warning on line 160 in src/lib/components/event/event-card.svelte

View workflow job for this annotation

GitHub Actions / lint

Each block should have a key
{@render eventLink(link)}
{@render eventNamespaceLink(link)}
{/each}
Expand All @@ -165,7 +165,7 @@

{#snippet eventSummary(value: Payload)}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm text-secondary/80">Summary</p>
<p class="min-w-56 text-sm font-medium text-secondary">Summary</p>
<p class="whitespace-pre-line">
<MetadataDecoder
{value}
Expand All @@ -182,7 +182,7 @@
{@const codeBlockValue = getCodeBlockValue(value)}
{@const stackTrace = getStackTrace(codeBlockValue)}
<div>
<p class="mb-1 min-w-56 text-sm text-secondary/80">
<p class="mb-1 min-w-56 text-sm font-medium text-secondary">
{format(key)}
</p>
{#if value?.payloads}
Expand Down Expand Up @@ -225,7 +225,7 @@
</div>
{#if stackTrace}
<div>
<p class="mb-1 min-w-56 text-sm text-secondary/80">
<p class="mb-1 min-w-56 text-sm font-medium text-secondary">
{translate('workflows.call-stack-tab')}
</p>
<CodeBlock
Expand All @@ -241,7 +241,7 @@

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Parameter 'key' implicitly has an 'any' type.
  • ⚠️ Parameter 'value' implicitly has an 'any' type.

{#snippet link(key, value)}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm text-secondary/80">
<p class="min-w-56 text-sm font-medium text-secondary">
{format(key)}
</p>
<Copyable
Expand All @@ -261,7 +261,7 @@

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Parameter 'key' implicitly has an 'any' type.
  • ⚠️ Parameter 'value' implicitly has an 'any' type.

{#snippet details(key, value)}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm text-secondary/80">
<p class="min-w-56 text-sm font-medium text-secondary">
{format(key)}
</p>
<p class="whitespace-pre-line break-all">
Expand Down
116 changes: 112 additions & 4 deletions src/lib/components/event/event-details-full.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<script lang="ts">
import { page } from '$app/state';

import type { EventGroup } from '$lib/models/event-groups/event-groups';
import type { WorkflowEvent } from '$lib/types/events';
import { formatAttributes } from '$lib/utilities/format-event-attributes';
import { displayLinkType } from '$lib/utilities/get-single-attribute-for-event';

import PendingActivityCard from '../workflow/pending-activity/pending-activity-card.svelte';
import PendingNexusOperationCard from '../workflow/pending-nexus-operation/pending-nexus-operation-card.svelte';

import EventCard from './event-card.svelte';
import EventDetailsSection from './event-details-section.svelte';
import EventPayloads from './event-payloads.svelte';

let {
group = undefined,
Expand All @@ -18,18 +24,120 @@
const showEventGroup = $derived(
group && (group.eventList.length > 1 || pendingEvent),
);

const processedEvents = $derived.by(() => {
if (!group || !showEventGroup) return [];
return group.eventList.map((evt) => {
const attrs = formatAttributes(evt);
if (evt?.principal?.name) attrs.principalName = evt.principal.name;
if (evt?.principal?.type) attrs.principalType = evt.principal.type;
const fields = Object.entries(attrs);
const payloadFields = fields.filter(
([_key, value]) =>
typeof value === 'object' && Object.keys(value).length > 0,
);
const linkFields = fields.filter(
([key, _value]) => displayLinkType(key, attrs) !== 'none',
);
const hiddenDetailFields = (() => {
if (evt.category === 'activity')
return ['scheduledEventId', 'startedEventId', 'namespaceId'];
if (evt.category === 'child-workflow')
return ['initiatedEventId', 'startedEventId', 'namespaceId'];
return ['namespaceId'];
})();
const detailFields = fields.filter(
([key, value]) =>
typeof value !== 'object' &&
displayLinkType(key, attrs) === 'none' &&
!hiddenDetailFields.includes(key) &&
(key !== 'namespace' ||
(key === 'namespace' && page.params.namespace !== value)),
);
return {
event: evt,
attributes: attrs,
payloadFields,
detailFields,
linkFields,
};
});
});

const payloadKeyOrder = [
'input',
'result',
'lastFailure',
'failure',
'header',
'searchAttributes',
'memo',
];

const allPayloadFields = $derived.by(() => {
const all = processedEvents.flatMap((p) => p.payloadFields);
return all.toSorted(([a], [b]) => {
const aIndex = payloadKeyOrder.indexOf(a);
const bIndex = payloadKeyOrder.indexOf(b);
const aOrder = aIndex !== -1 ? aIndex : payloadKeyOrder.length - 1;
const bOrder = bIndex !== -1 ? bIndex : payloadKeyOrder.length - 1;
return aOrder - bOrder;
});
});

const hasAnyPayloads = $derived(allPayloadFields.length > 0);
</script>

{#if showEventGroup}
<div class="flex flex-col overflow-hidden">
<div
class="surface-primary flex flex-col overflow-hidden border-t border-subtle"
>
{#if group?.pendingActivity}
<PendingActivityCard activity={group.pendingActivity} />
{:else if group?.pendingNexusOperation}
<PendingNexusOperationCard operation={group.pendingNexusOperation} />
{/if}
{#each group.eventList as groupEvent}
<EventCard event={groupEvent} />
{/each}

{#if hasAnyPayloads}
<div class="flex flex-col border-b border-subtle xl:flex-row">
<div class="flex w-full flex-col xl:w-1/2">
{#each processedEvents as processed, i (processed.event.id)}
<div
class="p-4 {i < processedEvents.length - 1
? 'border-b border-subtle'
: ''}"
>
<div class="flex flex-col gap-1">
<EventDetailsSection
event={processed.event}
{group}
attributes={processed.attributes}
detailFields={processed.detailFields}
linkFields={processed.linkFields}
/>
</div>
</div>
{/each}
</div>
<div class="flex w-full flex-col gap-1 p-4 xl:w-1/2">
<EventPayloads payloadFields={allPayloadFields} />
</div>
</div>
{:else}
{#each processedEvents as processed (processed.event.id)}
<div class="border-b border-subtle p-4">
<div class="flex flex-col gap-1">
<EventDetailsSection
event={processed.event}
{group}
attributes={processed.attributes}
detailFields={processed.detailFields}
linkFields={processed.linkFields}
/>
</div>
</div>
{/each}
{/if}
</div>
{:else if event}
<EventCard {event} />
Expand Down
171 changes: 171 additions & 0 deletions src/lib/components/event/event-details-section.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<script lang="ts">
import { page } from '$app/state';

import Timestamp from '$lib/components/timestamp.svelte';
import Copyable from '$lib/holocene/copyable/index.svelte';
import Link from '$lib/holocene/link.svelte';
import { translate } from '$lib/i18n/translate';
import type { EventGroup } from '$lib/models/event-groups/event-groups';
import type { WorkflowEvent } from '$lib/types/events';
import { getEventLinkHref } from '$lib/utilities/event-link-href';
import {
format,
spaceBetweenCapitalLetters,
} from '$lib/utilities/format-camel-case';
import type { CombinedAttributes } from '$lib/utilities/format-event-attributes';
import { formatDistanceAbbreviated } from '$lib/utilities/format-time';
import {
displayLinkType,
shouldDisplayAsTime,
} from '$lib/utilities/get-single-attribute-for-event';
import { isLocalActivityMarkerEvent } from '$lib/utilities/is-event-type';
import {
routeForEventHistoryEvent,
routeForNamespace,
} from '$lib/utilities/route-for';

import EventDetailsLink from './event-details-link.svelte';
import MetadataDecoder from './metadata-decoder.svelte';

let {
event,
group,
attributes,
detailFields,
linkFields,
}: {
event: WorkflowEvent;
group?: EventGroup;
attributes: CombinedAttributes;
detailFields: [string, unknown][];
linkFields: [string, unknown][];
} = $props();

const { namespace, workflow, run } = $derived(page.params);

const displayName = $derived(
isLocalActivityMarkerEvent(event)
? translate('events.category.local-activity')
: spaceBetweenCapitalLetters(event.name),
);

const durationSinceLastEvent = $derived.by(() => {
if (!group) return '';
const eventIndex = group.eventList.findIndex((evt) => evt.id === event.id);
if (eventIndex === -1 || eventIndex === 0) return '';
const previousEvent = group.eventList[eventIndex - 1];
return formatDistanceAbbreviated({
start: event.eventTime,
end: previousEvent.eventTime,
includeMilliseconds: true,
});
});
</script>

<div class="mb-2 flex w-full flex-wrap items-center justify-between gap-2">
<div class="flex items-center gap-2 text-base">
<Link
href={routeForEventHistoryEvent({
eventId: event.id,
run,
workflow,
namespace,
})}>{event.id}</Link
>
<p class="font-medium">
{displayName}
</p>
</div>
<div class="flex flex-col items-end gap-0 font-mono text-sm leading-4">
<Timestamp as="p" dateTime={event.eventTime} />
{#if durationSinceLastEvent}
<p class="font-medium text-secondary">+{durationSinceLastEvent}</p>
{/if}
</div>
</div>

{#if event?.links?.length}
{#each event.links as link}

Check warning on line 88 in src/lib/components/event/event-details-section.svelte

View workflow job for this annotation

GitHub Actions / lint

Each block should have a key
{@const href = getEventLinkHref(link)}
{@const value = href.split('workflows/')?.[1] || href}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm font-medium text-secondary">
{translate('nexus.link')}
</p>
<Copyable
copyIconTitle={translate('common.copy-icon-title')}
copySuccessIconTitle={translate('common.copy-success-icon-title')}
content={value}
>
<Link {href} class="whitespace-pre-line">{value}</Link>
</Copyable>
</div>
{@const nsHref = routeForNamespace({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • ⚠️ Type 'string | null | undefined' is not assignable to type 'string'.

namespace: link.workflowEvent.namespace,
})}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm font-medium text-secondary">
{translate('nexus.link-namespace')}
</p>
<Copyable
copyIconTitle={translate('common.copy-icon-title')}
copySuccessIconTitle={translate('common.copy-success-icon-title')}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • ⚠️ Type 'string | null | undefined' is not assignable to type 'string'.

content={link.workflowEvent.namespace}
>
<Link href={nsHref} class="whitespace-pre-line"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ 'link.workflowEvent' is possibly 'null' or 'undefined'.

>{link.workflowEvent.namespace}</Link
>
</Copyable>
</div>
{/each}
{/if}

{#if event?.userMetadata?.summary}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm font-medium text-secondary">Summary</p>
<p class="whitespace-pre-line">
<MetadataDecoder
value={event.userMetadata.summary}
let:decodedValue
fallback={translate('events.decode-failed')}
>
{decodedValue}
</MetadataDecoder>
</p>
</div>
{/if}

{#each detailFields as [key, value] (key)}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm font-medium text-secondary">
{format(key)}
</p>
<p class="whitespace-pre-line break-all">
{#if shouldDisplayAsTime(key)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'unknown' is not assignable to type 'ValidTime | null | undefined'.

<Timestamp dateTime={value} />
{:else}
{value}
{/if}
</p>
</div>
{/each}

{#each linkFields as [key, value] (key)}
<div class="flex items-start gap-4">
<p class="min-w-56 text-sm font-medium text-secondary">
{format(key)}
</p>
<Copyable
copyIconTitle={translate('common.copy-icon-title')}
copySuccessIconTitle={translate('common.copy-success-icon-title')}
content={String(value)}
>
<EventDetailsLink
value={String(value)}
{attributes}
type={displayLinkType(key, attributes)}
class="whitespace-pre-line"
/>
</Copyable>
</div>
{/each}
Loading
Loading