Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll to Event Id #2350

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
26 changes: 16 additions & 10 deletions src/lib/components/event/event-link.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
<script lang="ts">
import Badge from '$lib/holocene/badge.svelte';
import Link from '$lib/holocene/link.svelte';
import { translate } from '$lib/i18n/translate';
import type { EventLink } from '$lib/types/events';
import { routeForEventHistory } from '$lib/utilities/route-for';

export let link: EventLink;
export let value = link.workflowEvent.workflowId;
export let label = translate('common.link');
export let href = routeForEventHistory({
namespace: link.workflowEvent.namespace,
workflow: link.workflowEvent.workflowId,
run: link.workflowEvent.runId,
eventId: link.workflowEvent.eventRef?.eventId,
});
export let xs = false;
</script>

<div
class="flex flex-row items-center gap-2 overflow-hidden first:pt-0 last:border-b-0 xl:max-w-xl {$$props.class}"
>
<p class="max-w-fit whitespace-nowrap text-right text-xs">Link</p>
<div class="overflow-hidden truncate pr-1">
<p class="max-w-fit whitespace-nowrap text-right text-sm" class:text-xs={xs}>
{label}
</p>
<div class="overflow-hidden truncate">
<Badge type="subtle">
<Link
href={routeForEventHistory({
namespace: link.workflowEvent.namespace,
workflow: link.workflowEvent.workflowId,
run: link.workflowEvent.runId,
})}
>
{link.workflowEvent.workflowId}
<Link {href}>
{value}
</Link>
</Badge>
</div>
Expand Down
44 changes: 20 additions & 24 deletions src/lib/components/event/event-links-expanded.svelte
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
<script lang="ts">
import Badge from '$lib/holocene/badge.svelte';
import Link from '$lib/holocene/link.svelte';
import type { EventLink } from '$lib/types/events';
import { routeForEventHistory } from '$lib/utilities/route-for';
import { translate } from '$lib/i18n/translate';
import type { EventLink as ELink } from '$lib/types/events';
import { routeForNamespace } from '$lib/utilities/route-for';

export let links: EventLink[] = [];
import EventLink from './event-link.svelte';

export let links: ELink[] = [];
</script>

{#each links as link}
{#if link?.workflowEvent}
<div class="content">
<p class="text-sm">Link</p>
<Badge type="subtle">
<Link
href={routeForEventHistory({
namespace: link.workflowEvent.namespace,
workflow: link.workflowEvent.workflowId,
run: link.workflowEvent.runId,
})}
>
{link.workflowEvent.workflowId}
</Link>
</Badge>
<div
class="block flex w-full items-center gap-4 px-2 py-1 py-1 text-left text-left xl:flex"
>
<EventLink {link} />
</div>
<div
class="block flex w-full items-center gap-4 px-2 py-1 py-1 text-left text-left xl:flex"
>
<EventLink
{link}
label={translate('common.link-namespace')}
value={link.workflowEvent.namespace}
href={routeForNamespace({ namespace: link.workflowEvent.namespace })}
/>
</div>
{/if}
{/each}

<style lang="postcss">
.content {
@apply block flex w-full w-full items-center gap-4 px-2 py-1 py-1 text-left text-left xl:flex;
}
</style>
26 changes: 26 additions & 0 deletions src/lib/components/event/event-summary-row.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
export let active = false;
export let onRowClick: () => void = noop;

let row;

$: selectedId = isEventGroup(event)
? Array.from(event.events.keys()).pop()
: event.id;
Expand All @@ -57,6 +59,7 @@
workflow,
run,
});

$: expanded = expandAll;
$: attributes = formatAttributes(event);

Expand All @@ -78,6 +81,9 @@
: '';

const onLinkClick = () => {
if (isHashRow($page.url.hash) && expanded) {
history.replaceState(null, '', location.pathname + location.search);
}
expanded = !expanded;
onRowClick();
};
Expand Down Expand Up @@ -112,9 +118,29 @@
isEventGroup(event) &&
!event.isPending &&
event.eventList.find(isActivityTaskStartedEvent)?.attributes?.attempt;

const scrollToId = (hash: string) => {
if (isHashRow(hash)) {
expanded = true;
setTimeout(() => {
row?.scrollIntoView({ behavior: 'smooth' });
}, 500);
}
};

const isHashRow = (hash: string) => {
if (hash) {
const id = hash.slice(1);
return compact ? group?.eventIds.has(id) : event?.id === id;
}
return false;
};

$: scrollToId($page.url.hash);
</script>

<tr
bind:this={row}
class="row dense"
id={`${event.id}-${index}`}
class:expanded={expanded && !expandAll}
Expand Down
46 changes: 32 additions & 14 deletions src/lib/components/lines-and-dots/workflow-callback.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
<script lang="ts">
import Alert from '$lib/holocene/alert.svelte';
import Badge from '$lib/holocene/badge.svelte';
import CodeBlock from '$lib/holocene/code-block.svelte';
import { translate } from '$lib/i18n/translate';
import { fullEventHistory } from '$lib/stores/events';
import { timeFormat } from '$lib/stores/time-format';
import type { CallbackInfo } from '$lib/types';
import { formatDate } from '$lib/utilities/format-date';
import { routeForNamespace } from '$lib/utilities/route-for';

import EventLink from '../event/event-link.svelte';

export let callback: CallbackInfo;

$: completedTime = formatDate(callback.lastAttemptCompleteTime, $timeFormat);
$: nextTime = formatDate(callback.nextAttemptScheduleTime, $timeFormat);
$: failure = callback?.lastAttemptFailure?.message;

$: initialEvent = $fullEventHistory[0];
$: link = initialEvent?.links[0];

const titles = {
Standby: translate('nexus.callback.standby'),
Scheduled: translate('nexus.callback.scheduled'),
Expand All @@ -25,32 +33,42 @@

<Alert icon="nexus" intent="info" {title}>
<div class="flex flex-col gap-2 pt-2">
<div class="flex items-center gap-2">
<p>
{translate('common.url')}
<span class="badge">{callback.callback.nexus.url}</span>
</p>
</div>
{#if link}
<EventLink {link} />
<EventLink
{link}
label={translate('common.link-namespace')}
value={link.workflowEvent.namespace}
href={routeForNamespace({ namespace: link.workflowEvent.namespace })}
/>
{/if}
<div class="flex flex-col items-start gap-2 md:flex-row md:items-center">
<p>
{translate('common.state')} <span class="badge">{callback.state}</span>
<p class="flex items-center gap-2">
{translate('common.state')}
<Badge type="subtle">{callback.state}</Badge>
</p>
{#if callback.attempt}
<p>
<p class="flex items-center gap-2">
{translate('common.attempt')}
<span class="badge">{callback.attempt}</span>
<Badge type="subtle">
{callback.attempt}
</Badge>
</p>
{/if}
{#if callback.lastAttemptCompleteTime}
<p>
<p class="flex items-center gap-2">
{translate('nexus.last-attempt-completed-time')}
<span class="badge">{completedTime}</span>
<Badge type="subtle">
{completedTime}
</Badge>
</p>
{/if}
{#if callback.nextAttemptScheduleTime}
<p>
<p class="flex items-center gap-2">
{translate('nexus.next-attempt-scheduled-time')}
<span class="badge">{nextTime}</span>
<Badge type="subtle">
{nextTime}
</Badge>
</p>
{/if}
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/lib/holocene/table/paginated-table/paginated.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
$: url = $page.url;
$: perPageParam = url.searchParams.get(perPageKey) ?? pageSizeOptions[0];
$: currentPageParam = url.searchParams.get(currentPageKey) ?? '1';
$: store = pagination(items, perPageParam, currentPageParam);
$: hash = $page.url.hash;
$: store = pagination(items, perPageParam, currentPageParam, hash);

// keep the 'page-size' url search param within the supported options
$: {
Expand Down Expand Up @@ -83,7 +84,7 @@
};

$: {
if (currentPageParam) store.jumpToPage(currentPageParam);
if (currentPageParam && !hash) store.jumpToPage(currentPageParam);
if (perPageParam) store.adjustPageSize(perPageParam);
}
</script>
Expand Down
2 changes: 2 additions & 0 deletions src/lib/i18n/locales/en/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,6 @@ export const Strings = {
message: 'Message',
'upload-json': 'Upload JSON',
'input-valid-json': 'Input must be valid JSON',
link: 'Link',
'link-namespace': 'Link Namespace',
} as const;
54 changes: 54 additions & 0 deletions src/lib/stores/pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import {
getStartingIndexForPage,
getTotalPages,
getValidPage,
hasId,
pagination,
perPageFromSearchParameter,
} from './pagination';

const oneHundredResolutions = new Array(100).fill(null).map((_, i) => i);
const oneHundredItems = new Array(100)
.fill(null)
.map((_, i) => ({ id: i.toString() }));

describe('pagination', () => {
it('should have a pageSize', () => {
Expand Down Expand Up @@ -542,3 +546,53 @@ describe('perPageFromSearchParameter', () => {
expect(perPageFromSearchParameter({} as any)).toBe(100);
});
});

describe('getStartingIndexForPage', () => {
it('should return 0 for the first page', () => {
expect(getStartingIndexForPage(1, 20, oneHundredResolutions)).toBe(0);
});

it('should return the first index of the second page for the something on the second page', () => {
expect(getStartingIndexForPage(2, 20, oneHundredResolutions)).toBe(20);
});

it('should return the first index of the last page for the something out of bounds', () => {
expect(getStartingIndexForPage(100, 20, oneHundredResolutions)).toBe(80);
});

it('should return 0 for the something out of bounds if the total number of items is less than itemsPerPage', () => {
expect(getStartingIndexForPage(3, 101, oneHundredResolutions)).toBe(0);
});

it('should return 0 if given a negative number for the page', () => {
expect(getStartingIndexForPage(-10, 20, oneHundredResolutions)).toBe(0);
});

it('should return 0 if given NaN', () => {
expect(getStartingIndexForPage(NaN, 20, oneHundredResolutions)).toBe(0);
});
});

describe('hash included in pagination store', () => {
it('should return true if object has id', () => {
expect(hasId({ id: '1234', name: 'cats' })).toBe(true);
});

it('should return false if object does not have id', () => {
expect(hasId({ name: 'cats', startedId: 'asdf' })).toBe(false);
});

it('should not adjust page when hash is included and on first page', () => {
const store = pagination(oneHundredItems, 50, 0, '#23');
const { currentPage } = get(store);

expect(currentPage).toBe(1);
});

it('should adjust page when hash is included and is on next page', () => {
const store = pagination(oneHundredItems, 50, 0, '#87');
const { currentPage } = get(store);

expect(currentPage).toBe(2);
});
});
17 changes: 17 additions & 0 deletions src/lib/stores/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,33 @@ export const outOfBounds = (
return false;
};

export const hasId = (item: unknown): item is { id: string } => {
return (
typeof item === 'object' && Object.prototype.hasOwnProperty.call(item, 'id')
);
};

/**
* Creates a Svelte store for viewing pages of a larger data set.
*/
export const pagination = <T>(
items: Readonly<T[]> = [],
perPage: number | string = defaultItemsPerPage,
startingIndex: string | number = 0,
hash: string = '',
): PaginationStore<T> => {
perPage = perPageFromSearchParameter(perPage);

const hashId = hash?.slice(1);
if (hashId) {
const itemIndex = items.findIndex(
(item: unknown) => hasId(item) && item?.id === hashId,
);
if (itemIndex !== -1) {
startingIndex = itemIndex;
}
}

const start = getNearestStartingIndex(
toNumber(startingIndex),
perPage,
Expand Down
6 changes: 3 additions & 3 deletions src/lib/utilities/route-for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type RouteParameters = {
run: string;
view?: EventView | string;
queryParams?: Record<string, string>;
eventId: string;
eventId?: string;
scheduleId: string;
queue: string;
schedule: string;
Expand All @@ -38,7 +38,7 @@ export type ScheduleParameters = Pick<
>;
export type EventHistoryParameters = Pick<
RouteParameters,
'namespace' | 'workflow' | 'run' | 'view' | 'queryParams'
'namespace' | 'workflow' | 'run' | 'eventId' | 'view' | 'queryParams'
>;
export type EventParameters = Pick<
RouteParameters,
Expand Down Expand Up @@ -164,7 +164,7 @@ export const routeForEventHistory = ({
...parameters
}: EventHistoryParameters): string => {
const eventHistoryPath = `${routeForWorkflow(parameters)}/history`;
return toURL(`${eventHistoryPath}`, queryParams);
return toURL(`${eventHistoryPath}`, queryParams, parameters?.eventId);
};

export const routeForEventHistoryEvent = ({
Expand Down
Loading
Loading