Skip to content

Commit 73bbec2

Browse files
authored
feat: Plugin Overlay for static, high priority, immutable configuration (#1018)
* refactor: create store only once * feat: plugin overlay * chore: remove unused stubs * refactor: use refetchOnMountOrArgChange * chore: remove unused code
1 parent ca255a4 commit 73bbec2

File tree

28 files changed

+172
-126
lines changed

28 files changed

+172
-126
lines changed

packages/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export * from './src/utils';
55
export * from './src/StoreProvider';
66
export * from './src/PluginResolver';
77
export * from './src/usePluginSystem';
8+
export * from './src/usePluginOverlay';
89
export type {Plugin, PluginEntry} from './src/internal/Plugin';

packages/plugins/src/internal/Plugin.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {PluginOverlay} from './PluginOverlay';
12
import type {PluginScope} from './PluginScope';
23
import {PluginDetails as PluginDetailsSymbol, PluginInit} from './symbols';
34
import type {PluginConfig, PluginConfigInput, PluginDetails, PluginScopeStateFor, PluginState} from './types';
@@ -7,6 +8,8 @@ export class Plugin<T extends PluginState> {
78
public readonly [PluginInit]: (context: PluginScope<PluginScopeStateFor<T>>, config: T['config']) => void;
89
public readonly [PluginDetailsSymbol]: PluginDetails<T>;
910

11+
public readonly overlay = new PluginOverlay();
12+
1013
public constructor(
1114
config: PluginDetails<T>,
1215
initFn: (tk: PluginScope<PluginScopeStateFor<T>>, config: PluginConfig<T['config']>) => void
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {PluginOverlayContext} from './symbols';
2+
import {PluginProvider} from './types';
3+
4+
export class PluginOverlay {
5+
private [PluginOverlayContext]: Record<string, any> = {};
6+
private providerCreator: (() => PluginProvider<any>) | null = null;
7+
8+
public getContext() {
9+
return this[PluginOverlayContext];
10+
}
11+
12+
public setContext(context: Record<string, any> | ((oldContext: Record<string, any>) => Record<string, any>)) {
13+
if (typeof context === 'function') {
14+
this[PluginOverlayContext] = context(this[PluginOverlayContext]);
15+
return;
16+
}
17+
this[PluginOverlayContext] = context;
18+
}
19+
20+
public appendContext(context: Record<string, any>) {
21+
this[PluginOverlayContext] = {...this[PluginOverlayContext], ...context};
22+
}
23+
24+
public useProvider() {
25+
if (!this.providerCreator) return null;
26+
return this.providerCreator();
27+
}
28+
29+
public provider(pc: typeof this.providerCreator) {
30+
this.providerCreator = pc;
31+
}
32+
}

packages/plugins/src/internal/symbols.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export const PluginScopeScheduleUpdate = Symbol.for('schedule informing about ch
2020
export const PluginScopeEmitChange = Symbol.for('inform about data change');
2121
export const PluginScopeSubscribeChange = Symbol.for('subscribe to data change');
2222
export const PluginScopeListeners = Symbol.for('subscription listeners list');
23+
export const PluginOverlayContext = Symbol.for('context used only for overlay');
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {FC, PropsWithChildren, ReactElement, createElement, useCallback} from 'react';
2+
3+
import {Plugin, PluginEntry} from './internal/Plugin';
4+
import {PluginOverlay} from './internal/PluginOverlay';
5+
6+
export const usePluginOverlay = (plugins: PluginEntry<any>[]) => {
7+
const createProviderTree = useCallback((overlays: PluginOverlay[], children: ReactElement): ReactElement => {
8+
if (overlays.length === 0) {
9+
return children;
10+
}
11+
12+
const lastOverlay = overlays[overlays.length - 1];
13+
const provider = lastOverlay.useProvider();
14+
const newChildren = provider ? createElement(provider.type, provider.props, children) : children;
15+
16+
return createProviderTree(overlays.slice(0, -1), newChildren);
17+
}, []);
18+
19+
const ProviderComponent = useCallback<FC<PropsWithChildren<any>>>(
20+
({children}) =>
21+
createProviderTree(
22+
plugins.filter((plugin): plugin is Plugin<any> => 'overlay' in plugin).map(plugin => plugin.overlay),
23+
children
24+
),
25+
[createProviderTree]
26+
);
27+
28+
return ProviderComponent;
29+
};

packages/web/src/AppRoot.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {useMemo} from 'react';
33
import {Layout} from 'antd';
44
import {Content} from 'antd/lib/layout/layout';
55

6-
import {usePluginSystem} from '@testkube/plugins';
6+
import {usePluginOverlay, usePluginSystem} from '@testkube/plugins';
77

88
import {ReactComponent as Logo} from '@assets/testkube-symbol-color.svg';
99

@@ -26,7 +26,6 @@ import ModalPlugin from '@plugins/modal/plugin';
2626
import PermissionsPlugin from '@plugins/permissions/plugin';
2727
import PromoBannersPlugin from '@plugins/promo-banners/plugin';
2828
import RouterPlugin from '@plugins/router/plugin';
29-
import RtkResetOnApiChangePlugin from '@plugins/rtk-reset-on-api-change/plugin';
3029
import RtkPlugin from '@plugins/rtk/plugin';
3130
import SettingsPlugin from '@plugins/settings/plugin';
3231
import SiderCloudMigratePlugin from '@plugins/sider-cloud-migrate/plugin';
@@ -51,7 +50,6 @@ const AppRoot: React.FC = () => {
5150
ConfigPlugin.configure({slackUrl: externalLinks.slack}),
5251
RouterPlugin.configure({baseUrl: env.basename || ''}),
5352
PermissionsPlugin.configure({resolver: new BasePermissionsResolver()}),
54-
RtkResetOnApiChangePlugin,
5553
RtkPlugin,
5654
ModalPlugin,
5755
SiderLogoPlugin.configure({logo: <Logo />}),
@@ -67,6 +65,8 @@ const AppRoot: React.FC = () => {
6765
);
6866
const [PluginProvider, {routes}] = usePluginSystem(plugins);
6967

68+
const PluginOverlayProvider = usePluginOverlay(plugins);
69+
7070
return (
7171
<ErrorBoundary>
7272
<TelemetryProvider
@@ -76,18 +76,20 @@ const AppRoot: React.FC = () => {
7676
debug={env.debugTelemetry}
7777
paused
7878
>
79-
<PluginProvider>
80-
<Layout>
81-
<Sider />
82-
<StyledLayoutContentWrapper>
83-
<Content>
84-
<ErrorBoundary>
85-
<App routes={routes} />
86-
</ErrorBoundary>
87-
</Content>
88-
</StyledLayoutContentWrapper>
89-
</Layout>
90-
</PluginProvider>
79+
<PluginOverlayProvider>
80+
<PluginProvider>
81+
<Layout>
82+
<Sider />
83+
<StyledLayoutContentWrapper>
84+
<Content>
85+
<ErrorBoundary>
86+
<App routes={routes} />
87+
</ErrorBoundary>
88+
</Content>
89+
</StyledLayoutContentWrapper>
90+
</Layout>
91+
</PluginProvider>
92+
</PluginOverlayProvider>
9193
</TelemetryProvider>
9294
</ErrorBoundary>
9395
);

packages/web/src/components/molecules/LabelsSelect/LabelsSelect.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const LabelsSelect: React.FC<LabelsSelectProps> = props => {
5151
const {data, isFetching} = useGetLabelsQuery(null, {
5252
pollingInterval: PollingIntervals.default,
5353
skip: Boolean(options) || !isClusterAvailable,
54+
refetchOnMountOrArgChange: true,
5455
});
5556

5657
const formattedValue = useMemo(() => (value || []).map(label => ({label, value: label})), [value]);

packages/web/src/components/molecules/TestActionsDropdown/TestActionsDropdown.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ const TestActionsDropdown: React.FC<ActionsDropdownProps> = props => {
3939

4040
const {data: metrics, refetch} = useGetTestExecutionMetricsQuery(
4141
{id: name, last: 7, limit: 13},
42-
{skip: !isInViewport || !isSystemAvailable, pollingInterval: PollingIntervals.halfMin}
42+
{
43+
skip: !isInViewport || !isSystemAvailable,
44+
pollingInterval: PollingIntervals.halfMin,
45+
refetchOnMountOrArgChange: true,
46+
}
4347
);
4448

4549
const executions: ExecutionMetrics[] = useMemo(() => metrics?.executions ?? [], [metrics]);

packages/web/src/components/molecules/TestSuiteActionsDropdown/TestSuiteActionsDropdown.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ const TestSuiteActionsDropdown: React.FC<ActionsDropdownProps> = props => {
3939

4040
const {data: metrics, refetch} = useGetTestSuiteExecutionMetricsQuery(
4141
{id: name, last: 7, limit: 13},
42-
{skip: !isInViewport || !isSystemAvailable, pollingInterval: PollingIntervals.halfMin}
42+
{
43+
skip: !isInViewport || !isSystemAvailable,
44+
pollingInterval: PollingIntervals.halfMin,
45+
refetchOnMountOrArgChange: true,
46+
}
4347
);
4448

4549
const executions: ExecutionMetrics[] = useMemo(() => metrics?.executions ?? [], [metrics]);

packages/web/src/components/organisms/EntityDetails/EntityDetailsLayer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,18 @@ const EntityDetailsLayer: FC<PropsWithChildren<EntityDetailsLayerProps>> = ({
7373
{
7474
pollingInterval: PollingIntervals.long,
7575
skip: !isSystemAvailable || daysFilterValue === undefined,
76+
refetchOnMountOrArgChange: true,
7677
}
7778
);
7879

7980
const {data: rawMetrics, refetch: refetchMetrics} = useGetMetrics(
8081
{id, last: daysFilterValue},
81-
{skip: !isSystemAvailable}
82+
{skip: !isSystemAvailable, refetchOnMountOrArgChange: true}
8283
);
8384
const {data: rawDetails, error} = useGetEntityDetails(id, {
8485
pollingInterval: PollingIntervals.long,
8586
skip: !isSystemAvailable,
87+
refetchOnMountOrArgChange: true,
8688
});
8789

8890
const isV2 = isTestSuiteV2(rawDetails);

0 commit comments

Comments
 (0)