Skip to content
Closed
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
22 changes: 22 additions & 0 deletions src/lib/components/capability-guard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts">
import type { Snippet } from 'svelte';

import { isCapabilityEnabled } from '$lib/stores/capability-enablement';
import type { Capabilities } from '$lib/types';

interface Props {
capability: keyof Capabilities;
children: Snippet;
fallback?: Snippet;
}

let { capability, children, fallback }: Props = $props();

const enabled = $derived(isCapabilityEnabled(capability));
</script>

{#if $enabled}
{@render children()}
{:else if fallback}
{@render fallback()}
{/if}
23 changes: 23 additions & 0 deletions src/lib/stores/capability-enablement.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
import type { Readable } from 'svelte/store';
import { derived } from 'svelte/store';

import { page } from '$app/stores';

import type { Capabilities } from '$lib/types';
import { minimumVersionRequired } from '$lib/utilities/version-check';

import { getFlagStore } from './feature-flags';
import { temporalVersion } from './versions';

const LOCAL_OVERRIDE_CAPABILITIES = new Set<keyof Capabilities>([
'serverlessDeployments',
]);

export function isCapabilityEnabled(
key: keyof Capabilities,
): Readable<boolean> {
if (!LOCAL_OVERRIDE_CAPABILITIES.has(key)) {
return derived(page, ($page) =>
Boolean($page.data?.systemInfo?.capabilities?.[key]),
Copy link
Collaborator

Choose a reason for hiding this comment

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

We also have page.data.namespace.namespaceInfo?.capabilities which is where workerHeartbeats is currently enabled/disabled. Maybe we want a distinction between isSystemCapabilityEnabled and isNamespaceCapabilityEnabled?

);
}

const localStore = getFlagStore(String(key));
return derived([page, localStore], ([$page, $local]) => {
const serverValue = $page.data?.systemInfo?.capabilities?.[key];
return serverValue ?? $local;
});
}

export const prefixSearchEnabled = derived(
[page, temporalVersion],
([$page, $temporalVersion]) => {
Expand Down
33 changes: 33 additions & 0 deletions src/lib/stores/feature-flags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { get } from 'svelte/store';

import { afterEach, describe, expect, it } from 'vitest';

import { getFlagStore, setFeatureFlag } from './feature-flags';

describe('setFeatureFlag', () => {
afterEach(() => {
getFlagStore('serverlessDeployments').set(false);
});

it('should enable a flag', () => {
setFeatureFlag('serverlessDeployments', true);
expect(get(getFlagStore('serverlessDeployments'))).toBe(true);
});

it('should disable a flag', () => {
setFeatureFlag('serverlessDeployments', true);
setFeatureFlag('serverlessDeployments', false);
expect(get(getFlagStore('serverlessDeployments'))).toBe(false);
});

it('should use a per-flag localStorage key', () => {
setFeatureFlag('serverlessDeployments', true);
setFeatureFlag('otherFlag', true);
expect(get(getFlagStore('serverlessDeployments'))).toBe(true);
expect(get(getFlagStore('otherFlag'))).toBe(true);
setFeatureFlag('serverlessDeployments', false);
expect(get(getFlagStore('serverlessDeployments'))).toBe(false);
expect(get(getFlagStore('otherFlag'))).toBe(true);
getFlagStore('otherFlag').set(false);
});
});
14 changes: 14 additions & 0 deletions src/lib/stores/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { persistStore } from './persist-store';

const _stores = new Map<string, ReturnType<typeof persistStore<boolean>>>();

export function getFlagStore(key: string) {
if (!_stores.has(key)) {
_stores.set(key, persistStore<boolean>(`featureFlags.${key}`, false, true));
}
return _stores.get(key)!;
}

export function setFeatureFlag(key: string, enabled: boolean): void {
getFlagStore(key).set(enabled);
}
4 changes: 3 additions & 1 deletion src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export type GetClusterInfoResponse =
export type GetSystemInfoResponse =
temporal.api.workflowservice.v1.IGetSystemInfoResponse;
export type Capabilities =
temporal.api.workflowservice.v1.GetSystemInfoResponse.ICapabilities;
temporal.api.workflowservice.v1.GetSystemInfoResponse.ICapabilities & {
serverlessDeployments?: boolean | null;
};
export type GetWorkflowExecutionHistoryResponse =
temporal.api.workflowservice.v1.IGetWorkflowExecutionHistoryResponse;
export type GetSearchAttributesResponse =
Expand Down
Loading