diff --git a/README.md b/README.md
index 473569991..b093ef3f3 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
Website |
Documentation |
Twitter |
- Discord |
+ Slack |
Blog
diff --git a/package-lock.json b/package-lock.json
index 3f6ac114d..c0cc0dcc1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6655,9 +6655,9 @@
}
},
"node_modules/anser": {
- "version": "1.4.10",
- "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
- "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz",
+ "integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ=="
},
"node_modules/ansi-align": {
"version": "3.0.1",
@@ -6730,19 +6730,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/ansi-to-react": {
- "version": "6.1.6",
- "resolved": "https://registry.npmjs.org/ansi-to-react/-/ansi-to-react-6.1.6.tgz",
- "integrity": "sha512-+HWn72GKydtupxX9TORBedqOMsJRiKTqaLUKW8txSBZw9iBpzPKLI8KOu4WzwD4R7hSv1zEspobY6LwlWvwZ6Q==",
- "dependencies": {
- "anser": "^1.4.1",
- "escape-carriage": "^1.3.0"
- },
- "peerDependencies": {
- "react": "^16.3.2 || ^17.0.0",
- "react-dom": "^16.3.2 || ^17.0.0"
- }
- },
"node_modules/antd": {
"version": "4.24.14",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.24.14.tgz",
@@ -23959,12 +23946,13 @@
"@sentry/integrations": "^7.64.0",
"@sentry/react": "^7.64.0",
"@testkube/plugins": "*",
- "ansi-to-react": "^6.1.6",
+ "anser": "^2.1.1",
"antd": "^4.24.12",
"axios": "0.27.2",
"classnames": "2.3.1",
"cron-parser": "^4.8.1",
"date-fns": "^2.28.0",
+ "escape-carriage": "^1.3.1",
"file-saver": "^2.0.5",
"framer-motion": "^4.1.17",
"lodash.debounce": "^4.0.8",
diff --git a/packages/e2e-tests/fixtures/triggers.ts b/packages/e2e-tests/fixtures/triggers.ts
index c032531ef..8eba5de85 100644
--- a/packages/e2e-tests/fixtures/triggers.ts
+++ b/packages/e2e-tests/fixtures/triggers.ts
@@ -11,7 +11,7 @@ export default {
concurrencyPolicy: '',
testSelector: {
name: 'postman-executor-smoke',
- ...(config.cloudContext ? {} : {namespace: config.namespace}),
+ namespace: config.namespace,
},
resourceSelector: {
name: 'non-existant-resource',
diff --git a/packages/e2e-tests/helpers/common.ts b/packages/e2e-tests/helpers/common.ts
index 61d10392c..4559f439d 100644
--- a/packages/e2e-tests/helpers/common.ts
+++ b/packages/e2e-tests/helpers/common.ts
@@ -58,5 +58,5 @@ export function validateWebhook(webhookData: Partial, createdWebhoo
}
export function validateTrigger(triggerData: Partial, createdTriggerData: TriggerData): void {
- expect(triggerData).toEqual(createdTriggerData);
+ expect(createdTriggerData).toEqual(triggerData);
}
diff --git a/packages/plugins/README.md b/packages/plugins/README.md
index 594789256..9eaf8fef4 100644
--- a/packages/plugins/README.md
+++ b/packages/plugins/README.md
@@ -132,6 +132,12 @@ export default createPlugin('some-plugin-name')
// Using .provider() is more convenient though.
const isLoading = tk.sync(() => useSomeStoreData('loading'));
tk.slots.somePluginStub.someOtherSlot.add(<>Loading...>, {enabled: isLoading});
+
+ // When you need to ensure that the slot usage will be updated immediately after .sync() change,
+ // you may use .syncSubscribe() function instead.
+ // It's a bit slower, as it will emit the change to all scopes.
+ const isLoadingAsap = tk.syncSubscribe(() => useSomeStoreData('loading'));
+ tk.slots.somePluginStub.someOtherSlot.add(<>Loading...>, {enabled: isLoadingAsap});
});
```
diff --git a/packages/plugins/src/internal/PluginScope.spec.ts b/packages/plugins/src/internal/PluginScope.spec.ts
index fd9617a4b..0ff6f673e 100644
--- a/packages/plugins/src/internal/PluginScope.spec.ts
+++ b/packages/plugins/src/internal/PluginScope.spec.ts
@@ -265,6 +265,103 @@ describe('plugins', () => {
);
});
+ it('should return default value before synchronization (syncSubscribe)', () => {
+ const scope = create({});
+ const fn1 = jest.fn(() => Math.random());
+ const fn2 = jest.fn(() => Math.random());
+ const fnSync1 = scope.syncSubscribe(fn1);
+ const fnSync2 = scope.syncSubscribe(fn2, 1.5);
+ expect(fn1).not.toHaveBeenCalled();
+ expect(fn2).not.toHaveBeenCalled();
+ expect(fnSync1()).toBe(undefined);
+ expect(fnSync2()).toBe(1.5);
+ });
+
+ it('should return cached value after synchronization (syncSubscribe)', () => {
+ const scope = create({});
+ const fn1 = jest.fn(() => Math.random());
+ const fn2 = jest.fn(() => Math.random());
+ const fnSync1 = scope.syncSubscribe(fn1);
+ const fnSync2 = scope.syncSubscribe(fn2, 1.5);
+ scope[PluginScopeCallSync]();
+ expect(fn1).toHaveBeenCalledTimes(1);
+ expect(fn2).toHaveBeenCalledTimes(1);
+ expect(fnSync1()).toBe(fn1.mock.results[0].value);
+ expect(fnSync2()).toBe(fn2.mock.results[0].value);
+ });
+
+ it('should replace value after multiple synchronizations (syncSubscribe)', () => {
+ const scope = create({});
+ const fn = jest.fn(() => Math.random());
+ const fnSync = scope.syncSubscribe(fn);
+ scope[PluginScopeCallSync]();
+ scope[PluginScopeCallSync]();
+ expect(fn).toHaveBeenCalledTimes(2);
+ expect(fnSync()).toBe(fn.mock.results[1].value);
+ });
+
+ it('should emit change in root scope on initial run when its different than default (syncSubscribe)', async () => {
+ const root = create({});
+ const middle = create({}, root);
+ const scope = create({}, middle);
+ const listener = jest.fn();
+ root[PluginScopeSubscribeChange](listener);
+ scope.syncSubscribe(() => 10);
+ scope[PluginScopeCallSync]();
+ await frame();
+ expect(listener).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not emit change in root scope on initial run when its same as default (syncSubscribe)', async () => {
+ const root = create({});
+ const middle = create({}, root);
+ const scope = create({}, middle);
+ const listener = jest.fn();
+ root[PluginScopeSubscribeChange](listener);
+ scope.syncSubscribe(() => 10, 10);
+ scope[PluginScopeCallSync]();
+ await frame();
+ expect(listener).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not emit change in root scope when there is no change (syncSubscribe)', async () => {
+ const root = create({});
+ const middle = create({}, root);
+ const scope = create({}, middle);
+ const listener = jest.fn();
+ root[PluginScopeSubscribeChange](listener);
+ const value = 10;
+ const fn = jest.fn(() => value);
+ scope.syncSubscribe(fn, value);
+ scope[PluginScopeCallSync]();
+ scope[PluginScopeCallSync]();
+ await frame();
+ expect(listener).toHaveBeenCalledTimes(0);
+ });
+
+ it('should emit change in root scope when there is a change (syncSubscribe)', async () => {
+ const root = create({});
+ const middle = create({}, root);
+ const scope = create({}, middle);
+ const listener = jest.fn();
+ root[PluginScopeSubscribeChange](listener);
+ let value = 10;
+ scope.syncSubscribe(() => value, value);
+ scope[PluginScopeCallSync]();
+ value = 123;
+ scope[PluginScopeCallSync]();
+ await frame();
+ expect(listener).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not allow synchronization after plugin initialization (syncSubscribe)', () => {
+ const scope = create({});
+ scope[PluginScopeDisableNewSync]();
+ expect(() => scope.syncSubscribe(() => {})).toThrow(
+ new Error('The syncSubscribe() factory may be executed only during initialization.')
+ );
+ });
+
it('should allow destroying items produced by specific scope or its children', () => {
const root = create({slots: ['slot1']});
const separate = create({inheritedSlots: ['slot1']}, root);
diff --git a/packages/plugins/src/internal/PluginScope.ts b/packages/plugins/src/internal/PluginScope.ts
index ea5379278..ddff6f9c5 100644
--- a/packages/plugins/src/internal/PluginScope.ts
+++ b/packages/plugins/src/internal/PluginScope.ts
@@ -125,7 +125,7 @@ export class PluginScope {
public [PluginScopeCallSync](): void {
Array.from(this[PluginScopeSyncData].keys()).forEach(fn => {
- this[PluginScopeSyncData].set(fn, fn());
+ this[PluginScopeSyncData].set(fn, fn(this[PluginScopeSyncData].get(fn)));
});
}
@@ -189,6 +189,36 @@ export class PluginScope {
return () => this[PluginScopeSyncData].get(wrappedFn) ?? defaultValue;
}
+ /**
+ * Transfer data from React to the plugin context.
+ *
+ * The difference from sync() is, that it will emit change in the scope when the value is different.
+ * It's a bit slower, as it's trigger a change on the root scope afterward.
+ *
+ * TODO: Consider using Observables instead, that could be passed to Slot.enabled?
+ */
+ public syncSubscribe(fn: () => U, defaultValue: U): () => U;
+ public syncSubscribe(fn: () => U, defaultValue?: undefined): () => U | undefined;
+ public syncSubscribe(fn: () => U, defaultValue?: U): () => U | undefined {
+ if (this[PluginScopeDisableNewSyncStatus]) {
+ throw new Error('The syncSubscribe() factory may be executed only during initialization.');
+ }
+
+ const wrappedFn = (prevValue: U | undefined = defaultValue) => {
+ const nextValue = fn();
+ let root: PluginScope = this;
+ while (root[PluginScopeParentScope]) {
+ root = root[PluginScopeParentScope];
+ }
+ if (prevValue !== nextValue) {
+ root[PluginScopeEmitChange]();
+ }
+ return nextValue;
+ };
+ this[PluginScopeSyncData].set(wrappedFn, undefined);
+ return () => this[PluginScopeSyncData].get(wrappedFn) ?? defaultValue;
+ }
+
public children>(plugin: U): PluginScope>> {
const scope = new PluginScope(this, {
data: [],
diff --git a/packages/web/package.json b/packages/web/package.json
index 51304e168..941f6f8d3 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -54,12 +54,13 @@
"@sentry/integrations": "^7.64.0",
"@sentry/react": "^7.64.0",
"@testkube/plugins": "*",
- "ansi-to-react": "^6.1.6",
+ "anser": "^2.1.1",
"antd": "^4.24.12",
"axios": "0.27.2",
"classnames": "2.3.1",
"cron-parser": "^4.8.1",
"date-fns": "^2.28.0",
+ "escape-carriage": "^1.3.1",
"file-saver": "^2.0.5",
"framer-motion": "^4.1.17",
"lodash.debounce": "^4.0.8",
diff --git a/packages/web/public/index.html b/packages/web/public/index.html
index 2e61be822..d1b865cd9 100644
--- a/packages/web/public/index.html
+++ b/packages/web/public/index.html
@@ -14,7 +14,7 @@
diff --git a/packages/web/src/AppRoot.tsx b/packages/web/src/AppRoot.tsx
index 6484a0535..64fd06716 100644
--- a/packages/web/src/AppRoot.tsx
+++ b/packages/web/src/AppRoot.tsx
@@ -48,7 +48,7 @@ const AppRoot: React.FC = () => {
() => [
...basePlugins,
ClusterStatusPlugin,
- ConfigPlugin.configure({discordUrl: externalLinks.discord}),
+ ConfigPlugin.configure({slackUrl: externalLinks.slack}),
RouterPlugin.configure({baseUrl: env.basename || ''}),
PermissionsPlugin.configure({resolver: new BasePermissionsResolver()}),
RtkResetOnApiChangePlugin,
diff --git a/packages/web/src/antd-theme/my-antd-theme.less b/packages/web/src/antd-theme/my-antd-theme.less
index 829b74fba..3d48b3c86 100644
--- a/packages/web/src/antd-theme/my-antd-theme.less
+++ b/packages/web/src/antd-theme/my-antd-theme.less
@@ -10,7 +10,7 @@
@layout-body-background: #111827;
@layout-header-background: #111827;
@font-family: 'Roboto', sans-serif;
-@code-family: 'Roboto Mono', sans-serif;
+@code-family: 'IBM Plex Mono', monospace;
// button
@btn-height-base: 40px;
@btn-default-bg: rgba(255, 255, 255, 0.05);
diff --git a/packages/web/src/assets/images/status-pages-mock-1.svg b/packages/web/src/assets/images/status-pages-mock-1.svg
index dc5eef7f4..180e7fa4f 100644
--- a/packages/web/src/assets/images/status-pages-mock-1.svg
+++ b/packages/web/src/assets/images/status-pages-mock-1.svg
@@ -1,95 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.styled.tsx b/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.styled.tsx
index 3fa68e13a..faf8fad6d 100644
--- a/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.styled.tsx
+++ b/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.styled.tsx
@@ -1,8 +1,8 @@
import styled from 'styled-components';
-export const StyledExecutorIcon = styled.div<{$noWidth: boolean}>`
- width: ${({$noWidth}) => ($noWidth ? 'unset' : '28px')};
- height: ${({$noWidth}) => ($noWidth ? 'unset' : '28px')};
+export const StyledExecutorIcon = styled.div<{$size: 'large' | 'small'}>`
+ width: ${({$size}) => ($size === 'large' ? '28px' : '16px')};
+ height: ${({$size}) => ($size === 'large' ? '28px' : '16px')};
svg {
width: 100%;
diff --git a/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.tsx b/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.tsx
index 3f6ed97af..b717657a1 100644
--- a/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.tsx
+++ b/packages/web/src/components/atoms/ExecutorIcon/ExecutorIcon.tsx
@@ -18,8 +18,8 @@ import {ReactComponent as TracetestIcon} from '@assets/tracetestIcon.svg';
import {StyledExecutorIcon} from './ExecutorIcon.styled';
type ExecutorIconProps = {
+ size?: 'large' | 'small';
type?: string;
- noWidth?: boolean;
};
export const executorIcons: Record = {
@@ -39,7 +39,7 @@ export const executorIcons: Record = {
};
const ExecutorIcon: React.FC = props => {
- const {type, noWidth = false} = props;
+ const {size = 'large', type} = props;
const icon = type ? executorIcons[type] : ;
@@ -47,7 +47,7 @@ const ExecutorIcon: React.FC = props => {
return ;
}
return (
-
+
{icon || }
);
diff --git a/packages/web/src/components/atoms/Icon/Icon.tsx b/packages/web/src/components/atoms/Icon/Icon.tsx
index 4453b23ac..bfed9f834 100644
--- a/packages/web/src/components/atoms/Icon/Icon.tsx
+++ b/packages/web/src/components/atoms/Icon/Icon.tsx
@@ -10,7 +10,7 @@ import {IconProps} from './types';
const {
CogIcon,
DocumentationIcon,
- DiscordIcon,
+ SlackIcon,
GitHubIcon,
PassedStatusIcon,
FailedStatusIcon,
@@ -25,7 +25,7 @@ const {
const iconsMap: Record = {
cog: CogIcon,
documentation: DocumentationIcon,
- discord: DiscordIcon,
+ slack: SlackIcon,
github: GitHubIcon,
passed: PassedStatusIcon,
failed: FailedStatusIcon,
diff --git a/packages/web/src/components/atoms/Icon/icons.tsx b/packages/web/src/components/atoms/Icon/icons.tsx
index 7553fab03..a0cb203eb 100644
--- a/packages/web/src/components/atoms/Icon/icons.tsx
+++ b/packages/web/src/components/atoms/Icon/icons.tsx
@@ -41,10 +41,17 @@ const GitHubIcon: React.FC = () => {
);
};
-const DiscordIcon: React.FC = () => {
+const SlackIcon: React.FC = () => {
return (
-