Compare commits
8 Commits
AUT-331
...
executions
Author | SHA1 | Date | |
---|---|---|---|
![]() |
759468630e | ||
![]() |
b0d73aabc2 | ||
![]() |
651df1e2a1 | ||
![]() |
386839d89f | ||
![]() |
82e36b29e4 | ||
![]() |
6747c120ac | ||
![]() |
ef3db21848 | ||
![]() |
51fa862461 |
@@ -7,12 +7,4 @@ module.exports = {
|
|||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'prettier',
|
'prettier',
|
||||||
],
|
],
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/ban-ts-comment': ['off'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
@@ -4,6 +4,4 @@ module.exports = {
|
|||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
setupFilesAfterEnv: ['./test/setup/global-hooks.ts'],
|
setupFilesAfterEnv: ['./test/setup/global-hooks.ts'],
|
||||||
globalTeardown: './test/setup/global-teardown.ts',
|
globalTeardown: './test/setup/global-teardown.ts',
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: ['src/graphql/queries/*.ts'],
|
|
||||||
};
|
};
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024" width="1024" height="1024" >
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="plate-fill" x1="-.2" y1="-.2" x2=".8" y2=".8">
|
|
||||||
<stop offset="0" stop-color="#5a62c4"></stop>
|
|
||||||
<stop offset="1" stop-color="#3940ab"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<style>
|
|
||||||
.cls-1{fill:#5059c9}.cls-2{fill:#7b83eb}
|
|
||||||
</style>
|
|
||||||
<filter id="person-shadow" x="-50%" y="-50%" width="300%" height="300%">
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="25"></feGaussianBlur>
|
|
||||||
<feOffset dy="25"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".25"></feFuncA>
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<filter id="back-plate-shadow" x="-50%" y="-50%" width="300%" height="300%">
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="24"></feGaussianBlur>
|
|
||||||
<feOffset dx="2" dy="24"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".6"></feFuncA>
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<filter id="tee-shadow" x="-50%" y="-50%" width="250%" height="250%">
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="12"></feGaussianBlur>
|
|
||||||
<feOffset dx="10" dy="20"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".2"></feFuncA>
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<clipPath id="back-plate-clip">
|
|
||||||
<path d="M684 432H512v-49.143A112 112 0 1 0 416 272a111.556 111.556 0 0 0 10.785 48H160a32.094 32.094 0 0 0-32 32v320a32.094 32.094 0 0 0 32 32h178.67c15.236 90.8 94.2 160 189.33 160 106.039 0 192-85.961 192-192V468a36 36 0 0 0-36-36z" fill="#fff"></path>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g id="small_person" filter="url(#person-shadow)">
|
|
||||||
<path id="Body" class="cls-1" d="M692 432h168a36 36 0 0 1 36 36v164a120 120 0 0 1-120 120 120 120 0 0 1-120-120V468a36 36 0 0 1 36-36z"></path>
|
|
||||||
<circle id="Head" class="cls-1" cx="776" cy="304" r="80"></circle>
|
|
||||||
</g>
|
|
||||||
<g id="Large_Person" filter="url(#person-shadow)">
|
|
||||||
<path id="Body-2" data-name="Body" class="cls-2" d="M372 432h312a36 36 0 0 1 36 36v204a192 192 0 0 1-192 192 192 192 0 0 1-192-192V468a36 36 0 0 1 36-36z"></path>
|
|
||||||
<circle id="Head-2" data-name="Head" class="cls-2" cx="528" cy="272" r="112"></circle>
|
|
||||||
</g>
|
|
||||||
<rect id="Back_Plate" x="128" y="320" width="384" height="384" rx="32" ry="32" filter="url(#back-plate-shadow)" clip-path="url(#back-plate-clip)" fill="url(#plate-fill)"></rect>
|
|
||||||
<path id="Letter_T" d="M399.365 445.855h-60.293v164.2h-38.418v-164.2h-60.02V414h158.73z" filter="url(#tee-shadow)" fill="#fff"></path>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,23 +0,0 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
|
||||||
import { URLSearchParams } from 'url';
|
|
||||||
import authScope from '../common/auth-scope';
|
|
||||||
|
|
||||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
response_type: 'code',
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
response_mode: 'query',
|
|
||||||
scope: authScope.join(' '),
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = `https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?${searchParams.toString()}`;
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
import generateAuthUrl from './generate-auth-url';
|
|
||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
import refreshToken from './refresh-token';
|
|
||||||
import isStillVerified from './is-still-verified';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'oAuthRedirectUrl',
|
|
||||||
label: 'OAuth Redirect URL',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
value: '{WEB_APP_URL}/app/microsoft-teams/connections/add',
|
|
||||||
placeholder: null,
|
|
||||||
description:
|
|
||||||
'When asked to input a redirect URL in Microsoft identity platform, enter the URL above.',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientId',
|
|
||||||
label: 'Client ID',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientSecret',
|
|
||||||
label: 'Client Secret',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
generateAuthUrl,
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
refreshToken,
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
const currentUser = await getCurrentUser($);
|
|
||||||
return !!currentUser.displayName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,27 +0,0 @@
|
|||||||
import { URLSearchParams } from 'node:url';
|
|
||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import authScope from '../common/auth-scope';
|
|
||||||
|
|
||||||
const refreshToken = async ($: IGlobalVariable) => {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
client_secret: $.auth.data.clientSecret as string,
|
|
||||||
grant_type: 'refresh_token',
|
|
||||||
refresh_token: $.auth.data.refreshToken as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data } = await $.http.post(
|
|
||||||
'https://login.microsoftonline.com/organizations/oauth2/v2.0/token',
|
|
||||||
params.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
accessToken: data.access_token,
|
|
||||||
expiresIn: data.expires_in,
|
|
||||||
scope: authScope.join(' '),
|
|
||||||
tokenType: data.token_type,
|
|
||||||
refreshToken: data.refresh_token,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default refreshToken;
|
|
@@ -1,53 +0,0 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
import authScope from '../common/auth-scope';
|
|
||||||
import { URLSearchParams } from 'node:url';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
scope: authScope.join(' '),
|
|
||||||
code: $.auth.data.code as string,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
client_secret: $.auth.data.clientSecret as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post(
|
|
||||||
`https://login.microsoftonline.com/organizations/oauth2/v2.0/token`,
|
|
||||||
params.toString(),
|
|
||||||
{ headers }
|
|
||||||
);
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
accessToken: data.access_token,
|
|
||||||
tokenType: data.token_type,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUser = await getCurrentUser($);
|
|
||||||
|
|
||||||
const screenName = [currentUser.displayName, $.auth.data.mail]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' @ ');
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
clientId: $.auth.data.clientId,
|
|
||||||
clientSecret: $.auth.data.clientSecret,
|
|
||||||
scope: data.scope,
|
|
||||||
expiresIn: data.expires_in,
|
|
||||||
extExpiresIn: data.ext_expires_in,
|
|
||||||
refreshToken: data.refresh_token,
|
|
||||||
screenName,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,13 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
requestConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
||||||
|
|
||||||
if ($.auth.data?.accessToken) {
|
|
||||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,9 +0,0 @@
|
|||||||
const authScope: string[] = [
|
|
||||||
'offline_access',
|
|
||||||
'email',
|
|
||||||
'User.Read',
|
|
||||||
'openid',
|
|
||||||
'profile',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default authScope;
|
|
@@ -1,8 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
|
||||||
const response = await $.http.get('/v1.0/me');
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getCurrentUser;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import addAuthHeader from './common/add-auth-header';
|
|
||||||
import auth from './auth';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Microsoft Teams',
|
|
||||||
key: 'microsoft-teams',
|
|
||||||
baseUrl: 'https://teams.live.com',
|
|
||||||
apiBaseUrl: 'https://graph.microsoft.com',
|
|
||||||
iconUrl: '{BASE_URL}/apps/microsoft-teams/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/microsoft-teams/connection',
|
|
||||||
primaryColor: '464EB8',
|
|
||||||
supportsConnections: true,
|
|
||||||
beforeRequest: [addAuthHeader],
|
|
||||||
auth,
|
|
||||||
});
|
|
@@ -1,189 +0,0 @@
|
|||||||
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create card',
|
|
||||||
key: 'createCard',
|
|
||||||
description: 'Creates a new card within a specified board and list.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Board',
|
|
||||||
key: 'boardId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listBoards',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'List',
|
|
||||||
key: 'listId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
dependsOn: ['parameters.boardId'],
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listBoardLists',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.boardId',
|
|
||||||
value: '{parameters.boardId}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Description',
|
|
||||||
key: 'description',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
label: 'Label',
|
|
||||||
key: 'label',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
dependsOn: ['parameters.boardId'],
|
|
||||||
description: 'Select a color tag to attach to the card.',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listBoardLabels',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.boardId',
|
|
||||||
value: '{parameters.boardId}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Card Position',
|
|
||||||
key: 'cardPosition',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'top',
|
|
||||||
value: 'top',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'bottom',
|
|
||||||
value: 'bottom',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Members',
|
|
||||||
key: 'memberIds',
|
|
||||||
type: 'dynamic' as const,
|
|
||||||
required: false,
|
|
||||||
description: '',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: 'Member',
|
|
||||||
key: 'memberId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
dependsOn: ['parameters.boardId'],
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listMembers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.boardId',
|
|
||||||
value: '{parameters.boardId}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Due Date',
|
|
||||||
key: 'dueDate',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'Format: mm-dd-yyyy HH:mm:ss or yyyy-MM-dd HH:mm:ss.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'URL Attachment',
|
|
||||||
key: 'urlSource',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'A URL to attach to the card.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const {
|
|
||||||
listId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
cardPosition,
|
|
||||||
dueDate,
|
|
||||||
label,
|
|
||||||
urlSource,
|
|
||||||
} = $.step.parameters;
|
|
||||||
|
|
||||||
const memberIds = $.step.parameters.memberIds as IJSONArray;
|
|
||||||
const idMembers = memberIds.map(
|
|
||||||
(memberId: IJSONObject) => memberId.memberId
|
|
||||||
);
|
|
||||||
|
|
||||||
const fields = {
|
|
||||||
name,
|
|
||||||
desc: description,
|
|
||||||
idList: listId,
|
|
||||||
pos: cardPosition,
|
|
||||||
due: dueDate,
|
|
||||||
idMembers: idMembers.join(','),
|
|
||||||
idLabels: label,
|
|
||||||
urlSource,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.post('/1/cards', fields);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,3 +0,0 @@
|
|||||||
import createCard from './create-card';
|
|
||||||
|
|
||||||
export default [createCard];
|
|
@@ -5,8 +5,6 @@ const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|||||||
requestConfig.headers.Authorization = `OAuth oauth_consumer_key="${$.auth.data.apiKey}", oauth_token="${$.auth.data.token}"`;
|
requestConfig.headers.Authorization = `OAuth oauth_consumer_key="${$.auth.data.apiKey}", oauth_token="${$.auth.data.token}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestConfig.headers.Accept = 'application/json';
|
|
||||||
|
|
||||||
return requestConfig;
|
return requestConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
import listBoardLabels from './list-board-labels';
|
|
||||||
import listBoardLists from './list-board-lists';
|
|
||||||
import listBoards from './list-boards';
|
|
||||||
import listMembers from './listMembers';
|
|
||||||
|
|
||||||
export default [listBoardLabels, listBoardLists, listBoards, listMembers];
|
|
@@ -1,39 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List board labels',
|
|
||||||
key: 'listBoardLabels',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const boardLabels: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const boardId = $.step.parameters.boardId;
|
|
||||||
|
|
||||||
if (!boardId) {
|
|
||||||
return boardLabels;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
fields: 'color',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.get(`/1/boards/${boardId}/labels`, {
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data?.length) {
|
|
||||||
for (const boardLabel of data) {
|
|
||||||
boardLabels.data.push({
|
|
||||||
value: boardLabel.id,
|
|
||||||
name: boardLabel.color,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return boardLabels;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,33 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List board lists',
|
|
||||||
key: 'listBoardLists',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const boards: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const boardId = $.step.parameters.boardId;
|
|
||||||
|
|
||||||
if (!boardId) {
|
|
||||||
return boards;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await $.http.get(`/1/boards/${boardId}/lists`);
|
|
||||||
|
|
||||||
if (data?.length) {
|
|
||||||
for (const list of data) {
|
|
||||||
boards.data.push({
|
|
||||||
value: list.id,
|
|
||||||
name: list.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return boards;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,27 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List boards',
|
|
||||||
key: 'listBoards',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const boards: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.get(`/1/members/me/boards`);
|
|
||||||
|
|
||||||
if (data?.length) {
|
|
||||||
for (const board of data) {
|
|
||||||
boards.data.push({
|
|
||||||
value: board.id,
|
|
||||||
name: board.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return boards;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,33 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List members',
|
|
||||||
key: 'listMembers',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const members: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const boardId = $.step.parameters.boardId;
|
|
||||||
|
|
||||||
if (!boardId) {
|
|
||||||
return members;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await $.http.get(`/1/boards/${boardId}/members`);
|
|
||||||
|
|
||||||
if (data?.length) {
|
|
||||||
for (const member of data) {
|
|
||||||
members.data.push({
|
|
||||||
value: member.id,
|
|
||||||
name: member.fullName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return members;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,8 +1,6 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
import defineApp from '../../helpers/define-app';
|
||||||
import addAuthHeader from './common/add-auth-header';
|
import addAuthHeader from './common/add-auth-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import actions from './actions';
|
|
||||||
import dynamicData from './dynamic-data';
|
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Trello',
|
name: 'Trello',
|
||||||
@@ -15,6 +13,4 @@ export default defineApp({
|
|||||||
primaryColor: '0079bf',
|
primaryColor: '0079bf',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
|
||||||
dynamicData,
|
|
||||||
});
|
});
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="363" height="259" fill="#03363d"><path d="M173.82 40.5v112.86H80.34L173.82 40.5zm0-40.5a46.74 46.74 0 1 1-93.48 0h93.48zm15.4 153.37a46.74 46.74 0 0 1 93.48 0h-93.48zm0-40.5V0h93.5l-93.5 112.86zm52.28 137.06a18.22 18.22 0 0 0 12.95-5l6.42 6.93c-4.24 4.36-10.12 7.6-19.26 7.6-15.67 0-25.8-10.4-25.8-24.46a24 24 0 0 1 24.37-24.47c15.56 0 24.38 11.84 23.6 28.26H227c1.3 6.82 6.1 11.17 14.47 11.17m11.2-19c-1-6.37-4.8-11.06-12.4-11.06-7.07 0-12 4-13.27 11.06h25.68zM0 249.4l28.3-28.76H.67v-9.02h40.76v9.2l-28.3 28.75h28.7v9.03H0v-9.2zm73.6.52a18.22 18.22 0 0 0 12.95-5l6.42 6.93c-4.24 4.36-10.12 7.6-19.26 7.6-15.67 0-25.8-10.4-25.8-24.46a24 24 0 0 1 24.37-24.47c15.56 0 24.38 11.84 23.6 28.26H59.12c1.3 6.82 6.1 11.17 14.47 11.17m11.2-19c-1-6.37-4.8-11.06-12.4-11.06-7.07 0-12 4-13.27 11.06H84.8zm72.23 4.03c0-15 11.23-24.44 23.6-24.44a20.34 20.34 0 0 1 15.67 7.05v-27.72h10v68.6h-10V252a20.1 20.1 0 0 1-15.76 7.42c-12 0-23.5-9.5-23.5-24.43m39.82-.1a14.92 14.92 0 1 0-14.91 15.32c8.6 0 14.9-6.86 14.9-15.32m73.48 13.6l9.06-4.7a13.44 13.44 0 0 0 12.08 6.86c5.66 0 8.6-2.9 8.6-6.2 0-3.76-5.47-4.6-11.42-5.83-8-1.7-16.33-4.33-16.33-14 0-7.43 7.07-14.3 18.2-14.2 8.77 0 15.3 3.48 19 9.1l-8.4 4.6a12.19 12.19 0 0 0-10.57-5.36c-5.38 0-8.12 2.63-8.12 5.64 0 3.38 4.34 4.32 11.14 5.83 7.74 1.7 16.5 4.23 16.5 14 0 6.48-5.66 15.22-19.06 15.13-9.8 0-16.7-3.95-20.67-10.9m66.9-10.87l-7.93 8.65v12.2h-10v-68.6h10v44.93l21.23-23.3h12.18l-18.4 20.1 18.88 26.88h-11.32l-14.63-20.86zM126.8 210.53c-11.9 0-21.85 7.7-21.85 20.5v27.45h10.2V232.3c0-7.7 4.43-12.32 12-12.32s11.33 4.6 11.33 12.32v26.18h10.14v-27.45c0-12.78-10-20.5-21.85-20.5"/></svg>
|
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,23 +0,0 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
|
||||||
import { URLSearchParams } from 'url';
|
|
||||||
import authScope from '../common/auth-scope';
|
|
||||||
|
|
||||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
response_type: 'code',
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
scope: authScope.join(' '),
|
|
||||||
});
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
url: `${
|
|
||||||
$.auth.data.instanceUrl
|
|
||||||
}/oauth/authorizations/new?${searchParams.toString()}`,
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,55 +0,0 @@
|
|||||||
import generateAuthUrl from './generate-auth-url';
|
|
||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
import isStillVerified from './is-still-verified';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'oAuthRedirectUrl',
|
|
||||||
label: 'OAuth Redirect URL',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
value: '{WEB_APP_URL}/app/zendesk/connections/add',
|
|
||||||
placeholder: null,
|
|
||||||
description: '',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'instanceUrl',
|
|
||||||
label: 'Zendesk Subdomain Url',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: 'https://{{subdomain}}.zendesk.com',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientId',
|
|
||||||
label: 'Client ID',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientSecret',
|
|
||||||
label: 'Client Secret',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
generateAuthUrl,
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
await getCurrentUser($);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,56 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONValue, IField } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
import scopes from '../common/auth-scope';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
await getAccessToken($);
|
|
||||||
|
|
||||||
const user = await getCurrentUser($);
|
|
||||||
const subdomain = extractSubdomain($.auth.data.instanceUrl);
|
|
||||||
const name = user.name as string;
|
|
||||||
const screenName = [name, subdomain].filter(Boolean).join(' @ ');
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
screenName,
|
|
||||||
apiToken: $.auth.data.apiToken,
|
|
||||||
instanceUrl: $.auth.data.instanceUrl,
|
|
||||||
email: $.auth.data.email,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAccessToken = async ($: IGlobalVariable) => {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
|
|
||||||
const response = await $.http.post(`/oauth/tokens`, {
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
code: $.auth.data.code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
scope: scopes.join(' '),
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
client_secret: $.auth.data.clientSecret as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
|
|
||||||
$.auth.data.accessToken = data.access_token;
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
clientId: $.auth.data.clientId,
|
|
||||||
clientSecret: $.auth.data.clientSecret,
|
|
||||||
accessToken: data.access_token,
|
|
||||||
tokenType: data.token_type,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function extractSubdomain(url: IJSONValue) {
|
|
||||||
const match = (url as string).match(/https:\/\/(.*?)\.zendesk\.com/);
|
|
||||||
if (match && match[1]) {
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,17 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
const { instanceUrl, tokenType, accessToken } = $.auth.data;
|
|
||||||
|
|
||||||
if (instanceUrl) {
|
|
||||||
requestConfig.baseURL = instanceUrl as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenType && accessToken) {
|
|
||||||
requestConfig.headers.Authorization = `${tokenType} ${$.auth.data.accessToken}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,3 +0,0 @@
|
|||||||
const authScope: string[] = ['read', 'write'];
|
|
||||||
|
|
||||||
export default authScope;
|
|
@@ -1,10 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
|
||||||
const response = await $.http.get('/api/v2/users/me');
|
|
||||||
const currentUser = response.data.user;
|
|
||||||
|
|
||||||
return currentUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getCurrentUser;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import addAuthHeader from './common/add-auth-headers';
|
|
||||||
import auth from './auth';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Zendesk',
|
|
||||||
key: 'zendesk',
|
|
||||||
baseUrl: 'https://zendesk.com/',
|
|
||||||
apiBaseUrl: '',
|
|
||||||
iconUrl: '{BASE_URL}/apps/zendesk/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/zendesk/connection',
|
|
||||||
primaryColor: '17494d',
|
|
||||||
supportsConnections: true,
|
|
||||||
beforeRequest: [addAuthHeader],
|
|
||||||
auth,
|
|
||||||
});
|
|
@@ -1,8 +1,6 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Execution from '../../models/execution';
|
import Execution from '../../models/execution';
|
||||||
import ExecutionStep from '../../models/execution-step';
|
import ExecutionStep from '../../models/execution-step';
|
||||||
import globalVariable from '../../helpers/global-variable';
|
|
||||||
import logger from '../../helpers/logger';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -24,25 +22,6 @@ const deleteFlow = async (
|
|||||||
})
|
})
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const triggerStep = await flow.getTriggerStep();
|
|
||||||
const trigger = await triggerStep?.getTriggerCommand();
|
|
||||||
|
|
||||||
if (trigger?.type === 'webhook' && trigger.unregisterHook) {
|
|
||||||
const $ = await globalVariable({
|
|
||||||
flow,
|
|
||||||
connection: await triggerStep.$relatedQuery('connection'),
|
|
||||||
app: await triggerStep.getApp(),
|
|
||||||
step: triggerStep,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await trigger.unregisterHook($);
|
|
||||||
} catch (error) {
|
|
||||||
// suppress error as the remote resource might have been already deleted
|
|
||||||
logger.debug(`Failed to unregister webhook for flow ${flow.id}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const executionIds = (
|
const executionIds = (
|
||||||
await flow.$relatedQuery('executions').select('executions.id')
|
await flow.$relatedQuery('executions').select('executions.id')
|
||||||
).map((execution: Execution) => execution.id);
|
).map((execution: Execution) => execution.id);
|
||||||
|
@@ -81,10 +81,6 @@ const duplicateFlow = async (
|
|||||||
parameters: updateStepVariables(step.parameters, newStepIds),
|
parameters: updateStepVariables(step.parameters, newStepIds),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (duplicatedStep.isTrigger) {
|
|
||||||
await duplicatedStep.updateWebhookUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
newStepIds[step.id] = duplicatedStep.id;
|
newStepIds[step.id] = duplicatedStep.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import * as license from '../../helpers/license.ee';
|
import * as license from '../../helpers/license.ee';
|
||||||
@@ -23,7 +22,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
||||||
|
|
||||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
jest.replaceProperty(appConfig, 'isCloud', false)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty license data', async () => {
|
it('should return empty license data', async () => {
|
||||||
@@ -37,9 +36,9 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
license: {
|
license: {
|
||||||
id: null,
|
id: null as string,
|
||||||
name: null,
|
name: null as string,
|
||||||
expireAt: null,
|
expireAt: null as string,
|
||||||
verified: false,
|
verified: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -64,7 +63,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
|
|
||||||
describe('and with cloud flag enabled', () => {
|
describe('and with cloud flag enabled', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.replaceProperty(appConfig, 'isCloud', true);
|
jest.replaceProperty(appConfig, 'isCloud', true)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return all license data', async () => {
|
it('should return all license data', async () => {
|
||||||
@@ -93,7 +92,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
|
|
||||||
describe('and with cloud flag disabled', () => {
|
describe('and with cloud flag disabled', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
jest.replaceProperty(appConfig, 'isCloud', false)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return all license data', async () => {
|
it('should return all license data', async () => {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
// @ts-nocheck
|
import request, { Test } from 'supertest';
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
import { createRole } from '../../../test/factories/role';
|
import { createRole } from '../../../test/factories/role';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
import { IRole, IUser } from '@automatisch/types';
|
||||||
|
|
||||||
describe('graphQL getCurrentUser query', () => {
|
describe('graphQL getCurrentUser query', () => {
|
||||||
describe('with unauthenticated user', () => {
|
describe('with unauthenticated user', () => {
|
||||||
@@ -31,7 +31,7 @@ describe('graphQL getCurrentUser query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
describe('with authenticated user', () => {
|
||||||
let role, currentUser, token, requestObject;
|
let role: IRole, currentUser: IUser, token: string, requestObject: Test;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({
|
role = await createRole({
|
||||||
@@ -70,12 +70,12 @@ describe('graphQL getCurrentUser query', () => {
|
|||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getCurrentUser: {
|
getCurrentUser: {
|
||||||
createdAt: currentUser.createdAt.getTime().toString(),
|
createdAt: (currentUser.createdAt as Date).getTime().toString(),
|
||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
fullName: currentUser.fullName,
|
fullName: currentUser.fullName,
|
||||||
id: currentUser.id,
|
id: currentUser.id,
|
||||||
role: { id: role.id, name: role.name },
|
role: { id: role.id, name: role.name },
|
||||||
updatedAt: currentUser.updatedAt.getTime().toString(),
|
updatedAt: (currentUser.updatedAt as Date).getTime().toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
import request, { Test } from 'supertest';
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
@@ -10,6 +9,14 @@ import { createFlow } from '../../../test/factories/flow';
|
|||||||
import { createStep } from '../../../test/factories/step';
|
import { createStep } from '../../../test/factories/step';
|
||||||
import { createExecution } from '../../../test/factories/execution';
|
import { createExecution } from '../../../test/factories/execution';
|
||||||
import { createExecutionStep } from '../../../test/factories/execution-step';
|
import { createExecutionStep } from '../../../test/factories/execution-step';
|
||||||
|
import {
|
||||||
|
IRole,
|
||||||
|
IUser,
|
||||||
|
IExecution,
|
||||||
|
IFlow,
|
||||||
|
IExecutionStep,
|
||||||
|
IStep,
|
||||||
|
} from '@automatisch/types';
|
||||||
|
|
||||||
describe('graphQL getExecutions query', () => {
|
describe('graphQL getExecutions query', () => {
|
||||||
const query = `
|
const query = `
|
||||||
@@ -72,26 +79,24 @@ describe('graphQL getExecutions query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and with correct permission', () => {
|
describe('and with correct permission and isCreator condition', () => {
|
||||||
let role,
|
let role: IRole,
|
||||||
currentUser,
|
currentUser: IUser,
|
||||||
anotherUser,
|
anotherUser: IUser,
|
||||||
token,
|
token: string,
|
||||||
flowOne,
|
requestObject: Test,
|
||||||
stepOneForFlowOne,
|
flowOne: IFlow,
|
||||||
stepTwoForFlowOne,
|
stepOneForFlowOne: IStep,
|
||||||
executionOne,
|
stepTwoForFlowOne: IStep,
|
||||||
flowTwo,
|
executionOne: IExecution,
|
||||||
stepOneForFlowTwo,
|
executionStepOneForExecutionOne: IExecutionStep,
|
||||||
stepTwoForFlowTwo,
|
executionStepTwoForExecutionOne: IExecutionStep,
|
||||||
executionTwo,
|
flowTwo: IFlow,
|
||||||
flowThree,
|
stepOneForFlowTwo: IStep,
|
||||||
stepOneForFlowThree,
|
stepTwoForFlowTwo: IStep,
|
||||||
stepTwoForFlowThree,
|
executionTwo: IExecution,
|
||||||
executionThree,
|
executionStepOneForExecutionTwo: IExecutionStep,
|
||||||
expectedResponseForExecutionOne,
|
executionStepTwoForExecutionTwo: IExecutionStep;
|
||||||
expectedResponseForExecutionTwo,
|
|
||||||
expectedResponseForExecutionThree;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({
|
role = await createRole({
|
||||||
@@ -99,6 +104,13 @@ describe('graphQL getExecutions query', () => {
|
|||||||
name: 'sample',
|
name: 'sample',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
currentUser = await createUser({
|
currentUser = await createUser({
|
||||||
roleId: role.id,
|
roleId: role.id,
|
||||||
fullName: 'Current User',
|
fullName: 'Current User',
|
||||||
@@ -124,13 +136,13 @@ describe('graphQL getExecutions query', () => {
|
|||||||
flowId: flowOne.id,
|
flowId: flowOne.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await createExecutionStep({
|
executionStepOneForExecutionOne = await createExecutionStep({
|
||||||
executionId: executionOne.id,
|
executionId: executionOne.id,
|
||||||
stepId: stepOneForFlowOne.id,
|
stepId: stepOneForFlowOne.id,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
await createExecutionStep({
|
executionStepTwoForExecutionOne = await createExecutionStep({
|
||||||
executionId: executionOne.id,
|
executionId: executionOne.id,
|
||||||
stepId: stepTwoForFlowOne.id,
|
stepId: stepTwoForFlowOne.id,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@@ -152,72 +164,33 @@ describe('graphQL getExecutions query', () => {
|
|||||||
flowId: flowTwo.id,
|
flowId: flowTwo.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await createExecutionStep({
|
executionStepOneForExecutionTwo = await createExecutionStep({
|
||||||
executionId: executionTwo.id,
|
executionId: executionTwo.id,
|
||||||
stepId: stepOneForFlowTwo.id,
|
stepId: stepOneForFlowTwo.id,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
await createExecutionStep({
|
executionStepTwoForExecutionTwo = await createExecutionStep({
|
||||||
executionId: executionTwo.id,
|
executionId: executionTwo.id,
|
||||||
stepId: stepTwoForFlowTwo.id,
|
stepId: stepTwoForFlowTwo.id,
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
});
|
});
|
||||||
|
|
||||||
flowThree = await createFlow({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
stepOneForFlowThree = await createStep({
|
it('should return executions data of the current user', async () => {
|
||||||
flowId: flowThree.id,
|
const response = await request(app)
|
||||||
});
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
stepTwoForFlowThree = await createStep({
|
const expectedResponsePayload = {
|
||||||
flowId: flowThree.id,
|
data: {
|
||||||
});
|
getExecutions: {
|
||||||
|
edges: [
|
||||||
executionThree = await createExecution({
|
|
||||||
flowId: flowThree.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionThree.id,
|
|
||||||
stepId: stepOneForFlowThree.id,
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createExecutionStep({
|
|
||||||
executionId: executionThree.id,
|
|
||||||
stepId: stepTwoForFlowThree.id,
|
|
||||||
status: 'failure',
|
|
||||||
});
|
|
||||||
|
|
||||||
expectedResponseForExecutionOne = {
|
|
||||||
node: {
|
|
||||||
createdAt: executionOne.createdAt.getTime().toString(),
|
|
||||||
flow: {
|
|
||||||
active: flowOne.active,
|
|
||||||
id: flowOne.id,
|
|
||||||
name: flowOne.name,
|
|
||||||
steps: [
|
|
||||||
{
|
{
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
id: executionOne.id,
|
|
||||||
status: 'success',
|
|
||||||
testRun: executionOne.testRun,
|
|
||||||
updatedAt: executionOne.updatedAt.getTime().toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expectedResponseForExecutionTwo = {
|
|
||||||
node: {
|
node: {
|
||||||
createdAt: executionTwo.createdAt.getTime().toString(),
|
createdAt: (flowTwo.createdAt as Date).getTime().toString(),
|
||||||
flow: {
|
flow: {
|
||||||
active: flowTwo.active,
|
active: flowTwo.active,
|
||||||
id: flowTwo.id,
|
id: flowTwo.id,
|
||||||
@@ -234,57 +207,35 @@ describe('graphQL getExecutions query', () => {
|
|||||||
id: executionTwo.id,
|
id: executionTwo.id,
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
testRun: executionTwo.testRun,
|
testRun: executionTwo.testRun,
|
||||||
updatedAt: executionTwo.updatedAt.getTime().toString(),
|
updatedAt: (executionTwo.updatedAt as Date)
|
||||||
|
.getTime()
|
||||||
|
.toString(),
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
{
|
||||||
expectedResponseForExecutionThree = {
|
|
||||||
node: {
|
node: {
|
||||||
createdAt: executionThree.createdAt.getTime().toString(),
|
createdAt: (flowOne.createdAt as Date).getTime().toString(),
|
||||||
flow: {
|
flow: {
|
||||||
active: flowThree.active,
|
active: flowOne.active,
|
||||||
id: flowThree.id,
|
id: flowOne.id,
|
||||||
name: flowThree.name,
|
name: flowOne.name,
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
|
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: executionThree.id,
|
id: executionOne.id,
|
||||||
status: 'failure',
|
status: 'success',
|
||||||
testRun: executionThree.testRun,
|
testRun: executionOne.testRun,
|
||||||
updatedAt: executionThree.updatedAt.getTime().toString(),
|
updatedAt: (executionOne.updatedAt as Date)
|
||||||
|
.getTime()
|
||||||
|
.toString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with isCreator condition', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: role.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return executions data of the current user', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getExecutions: {
|
|
||||||
edges: [
|
|
||||||
expectedResponseForExecutionTwo,
|
|
||||||
expectedResponseForExecutionOne,
|
|
||||||
],
|
],
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
},
|
},
|
||||||
@@ -293,197 +244,34 @@ describe('graphQL getExecutions query', () => {
|
|||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without isCreator condition', () => {
|
// it('should not return users data with password', async () => {
|
||||||
beforeEach(async () => {
|
// const query = `
|
||||||
await createPermission({
|
// query {
|
||||||
action: 'read',
|
// getUsers(limit: 10, offset: 0) {
|
||||||
subject: 'Execution',
|
// pageInfo {
|
||||||
roleId: role.id,
|
// currentPage
|
||||||
conditions: [],
|
// totalPages
|
||||||
});
|
// }
|
||||||
});
|
// totalCount
|
||||||
|
// edges {
|
||||||
|
// node {
|
||||||
|
// id
|
||||||
|
// fullName
|
||||||
|
// password
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `;
|
||||||
|
|
||||||
it('should return executions data of all users', async () => {
|
// const response = await requestObject.send({ query }).expect(400);
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
// expect(response.body.errors).toBeDefined();
|
||||||
data: {
|
// expect(response.body.errors[0].message).toEqual(
|
||||||
getExecutions: {
|
// 'Cannot query field "password" on type "User".'
|
||||||
edges: [
|
// );
|
||||||
expectedResponseForExecutionThree,
|
// });
|
||||||
expectedResponseForExecutionTwo,
|
|
||||||
expectedResponseForExecutionOne,
|
|
||||||
],
|
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with filters', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: role.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return executions data for the specified flow', async () => {
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
testRun
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
status
|
|
||||||
flow {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
steps {
|
|
||||||
iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getExecutions: {
|
|
||||||
edges: [expectedResponseForExecutionOne],
|
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only executions data with success status', async () => {
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
testRun
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
status
|
|
||||||
flow {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
steps {
|
|
||||||
iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getExecutions: {
|
|
||||||
edges: [expectedResponseForExecutionOne],
|
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only executions data within date range', async () => {
|
|
||||||
const createdAtFrom = executionOne.createdAt.getTime().toString();
|
|
||||||
|
|
||||||
const createdAtTo = executionOne.createdAt.getTime().toString();
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
testRun
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
status
|
|
||||||
flow {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
steps {
|
|
||||||
iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getExecutions: {
|
|
||||||
edges: [expectedResponseForExecutionOne],
|
|
||||||
pageInfo: { currentPage: 1, totalPages: 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -7,11 +7,11 @@ import paginate from '../../helpers/pagination';
|
|||||||
type Filters = {
|
type Filters = {
|
||||||
flowId?: string;
|
flowId?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
createdAt?: {
|
updatedAt?: {
|
||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
limit: number;
|
limit: number;
|
||||||
@@ -30,7 +30,9 @@ const getExecutions = async (
|
|||||||
|
|
||||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
const allExecutions = Execution.query();
|
const allExecutions = Execution.query();
|
||||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
const executionBaseQuery = conditions.isCreator
|
||||||
|
? userExecutions
|
||||||
|
: allExecutions;
|
||||||
|
|
||||||
const selectStatusStatement = `
|
const selectStatusStatement = `
|
||||||
case
|
case
|
||||||
@@ -46,10 +48,9 @@ const getExecutions = async (
|
|||||||
.joinRelated('executionSteps as execution_steps')
|
.joinRelated('executionSteps as execution_steps')
|
||||||
.select('executions.*', raw(selectStatusStatement))
|
.select('executions.*', raw(selectStatusStatement))
|
||||||
.groupBy('executions.id')
|
.groupBy('executions.id')
|
||||||
.orderBy('created_at', 'desc');
|
.orderBy('updated_at', 'desc');
|
||||||
|
|
||||||
const computedExecutions = Execution
|
const computedExecutions = Execution.query()
|
||||||
.query()
|
|
||||||
.with('executions', executions)
|
.with('executions', executions)
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
@@ -66,24 +67,20 @@ const getExecutions = async (
|
|||||||
computedExecutions.where('executions.status', filters.status);
|
computedExecutions.where('executions.status', filters.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters?.createdAt) {
|
if (filters?.updatedAt) {
|
||||||
const createdAtFilter = filters.createdAt;
|
const updatedAtFilter = filters.updatedAt;
|
||||||
if (createdAtFilter.from) {
|
if (updatedAtFilter.from) {
|
||||||
const isoFromDateTime = DateTime
|
const isoFromDateTime = DateTime.fromMillis(
|
||||||
.fromMillis(
|
parseInt(updatedAtFilter.from, 10)
|
||||||
parseInt(createdAtFilter.from, 10)
|
).toISO();
|
||||||
)
|
computedExecutions.where('executions.updated_at', '>=', isoFromDateTime);
|
||||||
.toISO();
|
|
||||||
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createdAtFilter.to) {
|
if (updatedAtFilter.to) {
|
||||||
const isoToDateTime = DateTime
|
const isoToDateTime = DateTime.fromMillis(
|
||||||
.fromMillis(
|
parseInt(updatedAtFilter.to, 10)
|
||||||
parseInt(createdAtFilter.to, 10)
|
).toISO();
|
||||||
)
|
computedExecutions.where('executions.updated_at', '<=', isoToDateTime);
|
||||||
.toISO();
|
|
||||||
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,262 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
|
||||||
import appConfig from '../../config/app';
|
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createRole } from '../../../test/factories/role';
|
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
|
||||||
import { createUser } from '../../../test/factories/user';
|
|
||||||
import { createFlow } from '../../../test/factories/flow';
|
|
||||||
import { createStep } from '../../../test/factories/step';
|
|
||||||
import { createConnection } from '../../../test/factories/connection';
|
|
||||||
|
|
||||||
describe('graphQL getFlow query', () => {
|
|
||||||
const query = (flowId) => {
|
|
||||||
return `
|
|
||||||
query {
|
|
||||||
getFlow(id: "${flowId}") {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
status
|
|
||||||
steps {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
key
|
|
||||||
appKey
|
|
||||||
iconUrl
|
|
||||||
webhookUrl
|
|
||||||
status
|
|
||||||
position
|
|
||||||
connection {
|
|
||||||
id
|
|
||||||
verified
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
parameters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const invalidToken = 'invalid-token';
|
|
||||||
const flow = await createFlow();
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query: query(flow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and without permissions', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const userWithoutPermissions = await createUser();
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
const flow = await createFlow();
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(flow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with correct permission', () => {
|
|
||||||
let currentUser, currentUserRole, currentUserFlow;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUserRole = await createRole();
|
|
||||||
currentUser = await createUser({ roleId: currentUserRole.id });
|
|
||||||
currentUserFlow = await createFlow({ userId: currentUser.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with isCreator condition', () => {
|
|
||||||
it('should return executions data of the current user', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
webhookPath: `/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
|
||||||
userId: currentUser.id,
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Test',
|
|
||||||
authenticationKey: 'test key',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
connectionId: actionConnection.id,
|
|
||||||
key: 'translateText',
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(currentUserFlow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getFlow: {
|
|
||||||
active: currentUserFlow.active,
|
|
||||||
id: currentUserFlow.id,
|
|
||||||
name: currentUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt
|
|
||||||
.getTime()
|
|
||||||
.toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without isCreator condition', () => {
|
|
||||||
it('should return executions data of all users', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
webhookPath: `/webhooks/flows/${anotherUserFlow.id}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Test',
|
|
||||||
authenticationKey: 'test key',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
connectionId: actionConnection.id,
|
|
||||||
key: 'translateText',
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(anotherUserFlow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getFlow: {
|
|
||||||
active: anotherUserFlow.active,
|
|
||||||
id: anotherUserFlow.id,
|
|
||||||
name: anotherUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${anotherUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt
|
|
||||||
.getTime()
|
|
||||||
.toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
@@ -6,20 +5,21 @@ import Crypto from 'crypto';
|
|||||||
import { createRole } from '../../../test/factories/role';
|
import { createRole } from '../../../test/factories/role';
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
import { createPermission } from '../../../test/factories/permission';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
import { IRole, IUser, IPermission } from '@automatisch/types';
|
||||||
import * as license from '../../helpers/license.ee';
|
import * as license from '../../helpers/license.ee';
|
||||||
|
|
||||||
describe('graphQL getRole query', () => {
|
describe('graphQL getRole query', () => {
|
||||||
let validRole,
|
let validRole: IRole,
|
||||||
invalidRoleId,
|
invalidRoleId: string,
|
||||||
queryWithValidRole,
|
queryWithValidRole: string,
|
||||||
queryWithInvalidRole,
|
queryWithInvalidRole: string,
|
||||||
userWithPermissions,
|
userWithPermissions: IUser,
|
||||||
userWithoutPermissions,
|
userWithoutPermissions: IUser,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions: string,
|
||||||
tokenWithoutPermissions,
|
tokenWithoutPermissions: string,
|
||||||
invalidToken,
|
invalidToken: string,
|
||||||
permissionOne,
|
permissionOne: IPermission,
|
||||||
permissionTwo;
|
permissionTwo: IPermission;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
validRole = await createRole();
|
validRole = await createRole();
|
||||||
|
@@ -1,22 +1,22 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
import { createRole } from '../../../test/factories/role';
|
import { createRole } from '../../../test/factories/role';
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
import { createPermission } from '../../../test/factories/permission';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
import { IRole, IUser } from '@automatisch/types';
|
||||||
import * as license from '../../helpers/license.ee';
|
import * as license from '../../helpers/license.ee';
|
||||||
|
|
||||||
describe('graphQL getRoles query', () => {
|
describe('graphQL getRoles query', () => {
|
||||||
let currentUserRole,
|
let currentUserRole: IRole,
|
||||||
roleOne,
|
roleOne: IRole,
|
||||||
roleSecond,
|
roleSecond: IRole,
|
||||||
query,
|
query: string,
|
||||||
userWithPermissions,
|
userWithPermissions: IUser,
|
||||||
userWithoutPermissions,
|
userWithoutPermissions: IUser,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions: string,
|
||||||
tokenWithoutPermissions,
|
tokenWithoutPermissions: string,
|
||||||
invalidToken;
|
invalidToken: string;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ name: 'Current user role' });
|
currentUserRole = await createRole({ name: 'Current user role' });
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
|
import { IUser } from '@automatisch/types';
|
||||||
import User from '../../models/user';
|
import User from '../../models/user';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
@@ -32,7 +32,7 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
describe('with authenticated user', () => {
|
||||||
let user, userToken;
|
let user: IUser, userToken: string;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
||||||
@@ -54,7 +54,7 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: { getTrialStatus: null },
|
data: { getTrialStatus: null as string },
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
@@ -82,7 +82,7 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: { getTrialStatus: null },
|
data: { getTrialStatus: null as string },
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
// @ts-nocheck
|
import request, { Test } from 'supertest';
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
import Crypto from 'crypto';
|
import Crypto from 'crypto';
|
||||||
import { createRole } from '../../../test/factories/role';
|
import { createRole } from '../../../test/factories/role';
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
import { createPermission } from '../../../test/factories/permission';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
import { IRole, IUser } from '@automatisch/types';
|
||||||
|
|
||||||
describe('graphQL getUser query', () => {
|
describe('graphQL getUser query', () => {
|
||||||
describe('with unauthenticated user', () => {
|
describe('with unauthenticated user', () => {
|
||||||
@@ -61,7 +61,11 @@ describe('graphQL getUser query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
describe('and correct permissions', () => {
|
||||||
let role, currentUser, anotherUser, token, requestObject;
|
let role: IRole,
|
||||||
|
currentUser: IUser,
|
||||||
|
anotherUser: IUser,
|
||||||
|
token: string,
|
||||||
|
requestObject: Test;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({
|
role = await createRole({
|
||||||
@@ -112,12 +116,12 @@ describe('graphQL getUser query', () => {
|
|||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getUser: {
|
getUser: {
|
||||||
createdAt: anotherUser.createdAt.getTime().toString(),
|
createdAt: (anotherUser.createdAt as Date).getTime().toString(),
|
||||||
email: anotherUser.email,
|
email: anotherUser.email,
|
||||||
fullName: anotherUser.fullName,
|
fullName: anotherUser.fullName,
|
||||||
id: anotherUser.id,
|
id: anotherUser.id,
|
||||||
role: { id: role.id, name: role.name },
|
role: { id: role.id, name: role.name },
|
||||||
updatedAt: anotherUser.updatedAt.getTime().toString(),
|
updatedAt: (anotherUser.updatedAt as Date).getTime().toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
// @ts-nocheck
|
import request, { Test } from 'supertest';
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
||||||
import { createRole } from '../../../test/factories/role';
|
import { createRole } from '../../../test/factories/role';
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
import { createPermission } from '../../../test/factories/permission';
|
||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
import { IRole, IUser } from '@automatisch/types';
|
||||||
|
|
||||||
describe('graphQL getUsers query', () => {
|
describe('graphQL getUsers query', () => {
|
||||||
const query = `
|
const query = `
|
||||||
@@ -61,7 +61,11 @@ describe('graphQL getUsers query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('and with correct permissions', () => {
|
describe('and with correct permissions', () => {
|
||||||
let role, currentUser, anotherUser, token, requestObject;
|
let role: IRole,
|
||||||
|
currentUser: IUser,
|
||||||
|
anotherUser: IUser,
|
||||||
|
token: string,
|
||||||
|
requestObject: Test;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole({
|
role = await createRole({
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../app';
|
import app from '../../app';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
|
@@ -799,14 +799,14 @@ type Notification {
|
|||||||
description: String
|
description: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ExecutionCreatedAtFilterInput {
|
input ExecutionUpdatedAtFilterInput {
|
||||||
from: String
|
from: String
|
||||||
to: String
|
to: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ExecutionFiltersInput {
|
input ExecutionFiltersInput {
|
||||||
flowId: String
|
flowId: String
|
||||||
createdAt: ExecutionCreatedAtFilterInput
|
updatedAt: ExecutionUpdatedAtFilterInput
|
||||||
status: String
|
status: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -275,10 +275,7 @@ class User extends Base {
|
|||||||
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext) {
|
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext) {
|
||||||
await super.$beforeUpdate(opt, queryContext);
|
await super.$beforeUpdate(opt, queryContext);
|
||||||
|
|
||||||
if (this.email) {
|
|
||||||
this.email = this.email.toLowerCase();
|
this.email = this.email.toLowerCase();
|
||||||
}
|
|
||||||
|
|
||||||
await this.generateHash();
|
await this.generateHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,8 +10,6 @@ export const createConnection = async (params: Partial<Connection> = {}) => {
|
|||||||
authenticationKey: 'test key',
|
authenticationKey: 'test key',
|
||||||
};
|
};
|
||||||
|
|
||||||
delete params.formattedData;
|
|
||||||
|
|
||||||
params.data = AES.encrypt(
|
params.data = AES.encrypt(
|
||||||
JSON.stringify(formattedData),
|
JSON.stringify(formattedData),
|
||||||
appConfig.encryptionKey
|
appConfig.encryptionKey
|
||||||
|
@@ -4,8 +4,6 @@ import { createFlow } from './flow';
|
|||||||
export const createExecution = async (params: Partial<Execution> = {}) => {
|
export const createExecution = async (params: Partial<Execution> = {}) => {
|
||||||
params.flowId = params?.flowId || (await createFlow()).id;
|
params.flowId = params?.flowId || (await createFlow()).id;
|
||||||
params.testRun = params?.testRun || false;
|
params.testRun = params?.testRun || false;
|
||||||
params.createdAt = params?.createdAt || new Date().toISOString();
|
|
||||||
params.updatedAt = params?.updatedAt || new Date().toISOString();
|
|
||||||
|
|
||||||
const [execution] = await global.knex
|
const [execution] = await global.knex
|
||||||
.table('executions')
|
.table('executions')
|
||||||
|
@@ -4,8 +4,6 @@ import { createUser } from './user';
|
|||||||
export const createFlow = async (params: Partial<Flow> = {}) => {
|
export const createFlow = async (params: Partial<Flow> = {}) => {
|
||||||
params.userId = params?.userId || (await createUser()).id;
|
params.userId = params?.userId || (await createUser()).id;
|
||||||
params.name = params?.name || 'Name your flow!';
|
params.name = params?.name || 'Name your flow!';
|
||||||
params.createdAt = params?.createdAt || new Date().toISOString();
|
|
||||||
params.updatedAt = params?.updatedAt || new Date().toISOString();
|
|
||||||
|
|
||||||
const [flow] = await global.knex.table('flows').insert(params).returning('*');
|
const [flow] = await global.knex.table('flows').insert(params).returning('*');
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ export const createStep = async (params: Partial<Step> = {}) => {
|
|||||||
params.position = params?.position || (lastStep?.position || 0) + 1;
|
params.position = params?.position || (lastStep?.position || 0) + 1;
|
||||||
params.status = params?.status || 'completed';
|
params.status = params?.status || 'completed';
|
||||||
params.appKey =
|
params.appKey =
|
||||||
params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook');
|
params?.appKey || (params.type === 'action' ? 'webhook' : 'deepl');
|
||||||
|
|
||||||
const [step] = await global.knex.table('steps').insert(params).returning('*');
|
const [step] = await global.knex.table('steps').insert(params).returning('*');
|
||||||
|
|
||||||
|
@@ -188,14 +188,6 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/mattermost/connection' },
|
{ text: 'Connection', link: '/apps/mattermost/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'Microsoft Teams',
|
|
||||||
collapsible: true,
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Connection', link: '/apps/microsoft-teams/connection' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: 'Miro',
|
text: 'Miro',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -385,10 +377,7 @@ export default defineConfig({
|
|||||||
text: 'Trello',
|
text: 'Trello',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [{ text: 'Connection', link: '/apps/trello/connection' }],
|
||||||
{ text: 'Actions', link: '/apps/trello/actions' },
|
|
||||||
{ text: 'Connection', link: '/apps/trello/connection' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Twilio',
|
text: 'Twilio',
|
||||||
@@ -446,12 +435,6 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/youtube/connection' },
|
{ text: 'Connection', link: '/apps/youtube/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'Zendesk',
|
|
||||||
collapsible: true,
|
|
||||||
collapsed: true,
|
|
||||||
items: [{ text: 'Connection', link: '/apps/zendesk/connection' }],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
'/': [
|
'/': [
|
||||||
{
|
{
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
# Microsoft Teams
|
|
||||||
|
|
||||||
:::info
|
|
||||||
This page explains the steps you need to follow to set up the Microsoft Teams
|
|
||||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
|
||||||
:::
|
|
||||||
|
|
||||||
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com).
|
|
||||||
2. Click **Identity** from the menu on the left.
|
|
||||||
3. Expand the **Applications** and click **App Registrations**.
|
|
||||||
4. In this page, click on **New registrations**.
|
|
||||||
5. Fill in the **Name** field.
|
|
||||||
6. Select the **Accounts in any organizational directory** option.
|
|
||||||
7. In Redirect URI, select **Web** as platform.
|
|
||||||
8. Copy **OAuth Redirect URL** from Automatisch to the **Redirect URI** field.
|
|
||||||
9. Click on the **Register** button at the end of the form.
|
|
||||||
10. Go to the **Authentication** tab and select **Access tokens (used for implicit flows)** in the **Implicit grant and hybrid flows** section.
|
|
||||||
11. Click on the **Save** button.
|
|
||||||
12. Go to the **Overview** tab.
|
|
||||||
13. Copy the **Application (client) ID** value to the `Client ID` field on Automatisch.
|
|
||||||
14. In the same page, click on the **Add a certificate or secret** link.
|
|
||||||
15. Click on the **New client secret** button.
|
|
||||||
16. Fill in the **Description**, **Expires**, **Start**, and **End** fields.
|
|
||||||
17. It is important to note that you need to reconnect your connection manually once the client secret expires.
|
|
||||||
18. and click on the **Add** button.
|
|
||||||
19. Copy the **Client Secret** value to the `Client Secret` field on Automatisch.
|
|
||||||
20. Click **Submit** button on Automatisch.
|
|
||||||
21. Congrats! Start using your new Microsoft Teams connection within the flows.
|
|
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/trello.svg
|
|
||||||
items:
|
|
||||||
- name: Create card
|
|
||||||
desc: Creates a new card within a specified board and list.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -1,21 +0,0 @@
|
|||||||
# Zendesk
|
|
||||||
|
|
||||||
:::info
|
|
||||||
This page explains the steps you need to follow to set up the Zendesk
|
|
||||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
|
||||||
:::
|
|
||||||
|
|
||||||
1. Fill `Zendesk Subdomain URL` with your dashboard URL, for example: `https://yourcompany.zendesk.com`.
|
|
||||||
2. Go to your Zendesk dashboard.
|
|
||||||
3. Click on **Zendesk Products** at the top right corner and click **Admin Center** from the dropdown.
|
|
||||||
4. Enter **App and integrations** section.
|
|
||||||
5. Click on **Zendesk API** from the sidebar.
|
|
||||||
6. Click on **OAuth Clients** tab.
|
|
||||||
7. Click on **Add OAuth Client** button.
|
|
||||||
8. Enter necessary information in the form.
|
|
||||||
9. Copy **OAuth Redirect URL** from Automatisch to **Redirect URLs** field in the form.
|
|
||||||
10. Enter your preferred client ID value in **Unique Identifier** field.
|
|
||||||
11. Save the form to complete creating the OAuth client.
|
|
||||||
12. Copy the `Unique identifier` value from the page to the `Client ID` field on Automatisch.
|
|
||||||
13. Copy the `Secret` value from the page to the `Client Secret` field on Automatisch.
|
|
||||||
14. Now, you can start using the Zendesk connection with Automatisch.
|
|
@@ -39,7 +39,6 @@ The following integrations are currently supported by Automatisch.
|
|||||||
- [Stripe](/apps/stripe/triggers)
|
- [Stripe](/apps/stripe/triggers)
|
||||||
- [Telegram](/apps/telegram-bot/actions)
|
- [Telegram](/apps/telegram-bot/actions)
|
||||||
- [Todoist](/apps/todoist/triggers)
|
- [Todoist](/apps/todoist/triggers)
|
||||||
- [Trello](/apps/trello/actions)
|
|
||||||
- [Twilio](/apps/twilio/triggers)
|
- [Twilio](/apps/twilio/triggers)
|
||||||
- [Twitter](/apps/twitter/triggers)
|
- [Twitter](/apps/twitter/triggers)
|
||||||
- [Typeform](/apps/typeform/triggers)
|
- [Typeform](/apps/typeform/triggers)
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024" width="1024" height="1024" >
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="plate-fill" x1="-.2" y1="-.2" x2=".8" y2=".8">
|
|
||||||
<stop offset="0" stop-color="#5a62c4"></stop>
|
|
||||||
<stop offset="1" stop-color="#3940ab"></stop>
|
|
||||||
</linearGradient>
|
|
||||||
<style>
|
|
||||||
.cls-1{fill:#5059c9}.cls-2{fill:#7b83eb}
|
|
||||||
</style>
|
|
||||||
<filter id="person-shadow" x="-50%" y="-50%" width="300%" height="300%">
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="25"></feGaussianBlur>
|
|
||||||
<feOffset dy="25"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".25"></feFuncA>
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
|
|
||||||
<filter id="back-plate-shadow" x="-50%" y="-50%" width="300%" height="300%">
|
|
||||||
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="24"></feGaussianBlur>
|
|
||||||
<feOffset dx="2" dy="24"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".6"></feFuncA>
|
|
||||||
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<filter id="tee-shadow" x="-50%" y="-50%" width="250%" height="250%">
|
|
||||||
<feGaussianBlur in="SourceAlpha" stdDeviation="12"></feGaussianBlur>
|
|
||||||
<feOffset dx="10" dy="20"></feOffset>
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncA type="linear" slope=".2"></feFuncA>
|
|
||||||
</feComponentTransfer>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<clipPath id="back-plate-clip">
|
|
||||||
<path d="M684 432H512v-49.143A112 112 0 1 0 416 272a111.556 111.556 0 0 0 10.785 48H160a32.094 32.094 0 0 0-32 32v320a32.094 32.094 0 0 0 32 32h178.67c15.236 90.8 94.2 160 189.33 160 106.039 0 192-85.961 192-192V468a36 36 0 0 0-36-36z" fill="#fff"></path>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g id="small_person" filter="url(#person-shadow)">
|
|
||||||
<path id="Body" class="cls-1" d="M692 432h168a36 36 0 0 1 36 36v164a120 120 0 0 1-120 120 120 120 0 0 1-120-120V468a36 36 0 0 1 36-36z"></path>
|
|
||||||
<circle id="Head" class="cls-1" cx="776" cy="304" r="80"></circle>
|
|
||||||
</g>
|
|
||||||
<g id="Large_Person" filter="url(#person-shadow)">
|
|
||||||
<path id="Body-2" data-name="Body" class="cls-2" d="M372 432h312a36 36 0 0 1 36 36v204a192 192 0 0 1-192 192 192 192 0 0 1-192-192V468a36 36 0 0 1 36-36z"></path>
|
|
||||||
<circle id="Head-2" data-name="Head" class="cls-2" cx="528" cy="272" r="112"></circle>
|
|
||||||
</g>
|
|
||||||
<rect id="Back_Plate" x="128" y="320" width="384" height="384" rx="32" ry="32" filter="url(#back-plate-shadow)" clip-path="url(#back-plate-clip)" fill="url(#plate-fill)"></rect>
|
|
||||||
<path id="Letter_T" d="M399.365 445.855h-60.293v164.2h-38.418v-164.2h-60.02V414h158.73z" filter="url(#tee-shadow)" fill="#fff"></path>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="363" height="259" fill="#03363d"><path d="M173.82 40.5v112.86H80.34L173.82 40.5zm0-40.5a46.74 46.74 0 1 1-93.48 0h93.48zm15.4 153.37a46.74 46.74 0 0 1 93.48 0h-93.48zm0-40.5V0h93.5l-93.5 112.86zm52.28 137.06a18.22 18.22 0 0 0 12.95-5l6.42 6.93c-4.24 4.36-10.12 7.6-19.26 7.6-15.67 0-25.8-10.4-25.8-24.46a24 24 0 0 1 24.37-24.47c15.56 0 24.38 11.84 23.6 28.26H227c1.3 6.82 6.1 11.17 14.47 11.17m11.2-19c-1-6.37-4.8-11.06-12.4-11.06-7.07 0-12 4-13.27 11.06h25.68zM0 249.4l28.3-28.76H.67v-9.02h40.76v9.2l-28.3 28.75h28.7v9.03H0v-9.2zm73.6.52a18.22 18.22 0 0 0 12.95-5l6.42 6.93c-4.24 4.36-10.12 7.6-19.26 7.6-15.67 0-25.8-10.4-25.8-24.46a24 24 0 0 1 24.37-24.47c15.56 0 24.38 11.84 23.6 28.26H59.12c1.3 6.82 6.1 11.17 14.47 11.17m11.2-19c-1-6.37-4.8-11.06-12.4-11.06-7.07 0-12 4-13.27 11.06H84.8zm72.23 4.03c0-15 11.23-24.44 23.6-24.44a20.34 20.34 0 0 1 15.67 7.05v-27.72h10v68.6h-10V252a20.1 20.1 0 0 1-15.76 7.42c-12 0-23.5-9.5-23.5-24.43m39.82-.1a14.92 14.92 0 1 0-14.91 15.32c8.6 0 14.9-6.86 14.9-15.32m73.48 13.6l9.06-4.7a13.44 13.44 0 0 0 12.08 6.86c5.66 0 8.6-2.9 8.6-6.2 0-3.76-5.47-4.6-11.42-5.83-8-1.7-16.33-4.33-16.33-14 0-7.43 7.07-14.3 18.2-14.2 8.77 0 15.3 3.48 19 9.1l-8.4 4.6a12.19 12.19 0 0 0-10.57-5.36c-5.38 0-8.12 2.63-8.12 5.64 0 3.38 4.34 4.32 11.14 5.83 7.74 1.7 16.5 4.23 16.5 14 0 6.48-5.66 15.22-19.06 15.13-9.8 0-16.7-3.95-20.67-10.9m66.9-10.87l-7.93 8.65v12.2h-10v-68.6h10v44.93l21.23-23.3h12.18l-18.4 20.1 18.88 26.88h-11.32l-14.63-20.86zM126.8 210.53c-11.9 0-21.85 7.7-21.85 20.5v27.45h10.2V232.3c0-7.7 4.43-12.32 12-12.32s11.33 4.6 11.33 12.32v26.18h10.14v-27.45c0-12.78-10-20.5-21.85-20.5"/></svg>
|
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,106 +0,0 @@
|
|||||||
const { AuthenticatedPage } = require('../authenticated-page');
|
|
||||||
const { RoleConditionsModal } = require('./role-conditions-modal');
|
|
||||||
|
|
||||||
export class AdminCreateRolePage extends AuthenticatedPage {
|
|
||||||
screenshotPath = '/admin/create-role'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
this.nameInput = page.getByTestId('name-input');
|
|
||||||
this.descriptionInput = page.getByTestId('description-input');
|
|
||||||
this.createButton = page.getByTestId('create-button');
|
|
||||||
this.connectionRow = page.getByTestId('Connection-permission-row');
|
|
||||||
this.executionRow = page.getByTestId('Execution-permission-row');
|
|
||||||
this.flowRow = page.getByTestId('Flow-permission-row');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {('Connection'|'Execution'|'Flow')} subject
|
|
||||||
*/
|
|
||||||
getRoleConditionsModal (subject) {
|
|
||||||
return new RoleConditionsModal(this.page, subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPermissionConfigs () {
|
|
||||||
const subjects = ['Connection', 'Flow', 'Execution'];
|
|
||||||
const permissionConfigs = [];
|
|
||||||
for (let subject of subjects) {
|
|
||||||
const row = this.getSubjectRow(subject);
|
|
||||||
const actionInputs = await this.getRowInputs(row);
|
|
||||||
Object.keys(actionInputs).forEach(action => {
|
|
||||||
permissionConfigs.push({
|
|
||||||
action,
|
|
||||||
locator: actionInputs[action],
|
|
||||||
subject,
|
|
||||||
row
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return permissionConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {(
|
|
||||||
* 'Connection' | 'Flow' | 'Execution'
|
|
||||||
* )} subject
|
|
||||||
*/
|
|
||||||
getSubjectRow (subject) {
|
|
||||||
const k = `${subject.toLowerCase()}Row`
|
|
||||||
if (this[k]) {
|
|
||||||
return this[k]
|
|
||||||
} else {
|
|
||||||
throw 'Unknown row'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async getRowInputs (row) {
|
|
||||||
const inputs = {
|
|
||||||
// settingsButton: row.getByTestId('permission-settings-button')
|
|
||||||
}
|
|
||||||
for (let input of ['create', 'read', 'update', 'delete', 'publish']) {
|
|
||||||
const testId = `${input}-checkbox`
|
|
||||||
if (await row.getByTestId(testId).count() > 0) {
|
|
||||||
inputs[input] = row.getByTestId(testId).locator('input');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inputs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async clickPermissionSettings (row) {
|
|
||||||
await row.getByTestId('permission-settings-button').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} subject
|
|
||||||
* @param {'create'|'read'|'update'|'delete'|'publish'} action
|
|
||||||
* @param {boolean} val
|
|
||||||
*/
|
|
||||||
async updateAction (subject, action, val) {
|
|
||||||
const row = await this.getSubjectRow(subject);
|
|
||||||
const inputs = await this.getRowInputs(row);
|
|
||||||
if (inputs[action]) {
|
|
||||||
if (await inputs[action].isChecked()) {
|
|
||||||
if (!val) {
|
|
||||||
await inputs[action].click();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (val) {
|
|
||||||
await inputs[action].click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(`${subject} does not have action ${action}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
const { faker } = require('@faker-js/faker');
|
|
||||||
const { AuthenticatedPage } = require('../authenticated-page');
|
|
||||||
|
|
||||||
export class AdminCreateUserPage extends AuthenticatedPage {
|
|
||||||
screenshot = '/admin/create-user';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
this.fullNameInput = page.getByTestId('full-name-input');
|
|
||||||
this.emailInput = page.getByTestId('email-input');
|
|
||||||
this.passwordInput = page.getByTestId('password-input');
|
|
||||||
this.roleInput = page.getByTestId('role.id-autocomplete');
|
|
||||||
this.createButton = page.getByTestId('create-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
seed (seed) {
|
|
||||||
faker.seed(seed || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateUser () {
|
|
||||||
return {
|
|
||||||
fullName: faker.person.fullName(),
|
|
||||||
email: faker.internet.email().toLowerCase(),
|
|
||||||
password: faker.internet.password()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
export class DeleteRoleModal {
|
|
||||||
screenshotPath = '/admin/delete-role-modal';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
this.page = page;
|
|
||||||
this.modal = page.getByTestId('delete-role-modal');
|
|
||||||
this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
|
|
||||||
this.deleteButton = this.modal.getByTestId('confirmation-confirm-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
async close () {
|
|
||||||
await this.page.click('body', {
|
|
||||||
position: { x: 10, y: 10 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
export class DeleteUserModal {
|
|
||||||
screenshotPath = '/admin/delete-modal';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
this.page = page;
|
|
||||||
this.modal = page.getByTestId('delete-user-modal');
|
|
||||||
this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
|
|
||||||
this.deleteButton = this.modal.getByTestId('confirmation-confirm-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
async close () {
|
|
||||||
await this.page.click('body', {
|
|
||||||
position: { x: 10, y: 10 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
const { AdminCreateRolePage } = require('./create-role-page')
|
|
||||||
|
|
||||||
export class AdminEditRolePage extends AdminCreateRolePage {
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
delete this.createButton;
|
|
||||||
this.updateButton = page.getByTestId('update-button');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
const { faker } = require('@faker-js/faker');
|
|
||||||
const { AuthenticatedPage } = require('../authenticated-page');
|
|
||||||
|
|
||||||
faker.seed(9002);
|
|
||||||
|
|
||||||
export class AdminEditUserPage extends AuthenticatedPage {
|
|
||||||
screenshot = '/admin/edit-user';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
this.fullNameInput = page.getByTestId('full-name-input');
|
|
||||||
this.emailInput = page.getByTestId('email-input');
|
|
||||||
this.roleInput = page.getByTestId('role.id-autocomplete');
|
|
||||||
this.updateButton = page.getByTestId('update-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
generateUser () {
|
|
||||||
return {
|
|
||||||
fullName: faker.person.fullName(),
|
|
||||||
email: faker.internet.email(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
const { AdminCreateUserPage } = require('./create-user-page');
|
|
||||||
const { AdminEditUserPage } = require('./edit-user-page');
|
|
||||||
const { AdminUsersPage } = require('./users-page');
|
|
||||||
|
|
||||||
const { AdminRolesPage } = require('./roles-page');
|
|
||||||
const { AdminCreateRolePage } = require('./create-role-page');
|
|
||||||
const { AdminEditRolePage } = require('./edit-role-page');
|
|
||||||
|
|
||||||
export const adminFixtures = {
|
|
||||||
adminUsersPage: async ({ page }, use) => {
|
|
||||||
await use(new AdminUsersPage(page));
|
|
||||||
},
|
|
||||||
adminCreateUserPage: async ({ page }, use) => {
|
|
||||||
await use(new AdminCreateUserPage(page));
|
|
||||||
},
|
|
||||||
adminEditUserPage: async ({page}, use) => {
|
|
||||||
await use(new AdminEditUserPage(page));
|
|
||||||
},
|
|
||||||
adminRolesPage: async ({ page}, use) => {
|
|
||||||
await use(new AdminRolesPage(page));
|
|
||||||
},
|
|
||||||
adminEditRolePage: async ({ page}, use) => {
|
|
||||||
await use(new AdminEditRolePage(page));
|
|
||||||
},
|
|
||||||
adminCreateRolePage: async ({ page}, use) => {
|
|
||||||
await use(new AdminCreateRolePage(page));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@@ -1,47 +0,0 @@
|
|||||||
export class RoleConditionsModal {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
* @param {('Connection'|'Execution'|'Flow')} subject
|
|
||||||
*/
|
|
||||||
constructor (page, subject) {
|
|
||||||
this.page = page;
|
|
||||||
this.modal = page.getByTestId(`${subject}-role-conditions-modal`);
|
|
||||||
this.modalBody = this.modal.getByTestId('role-conditions-modal-body');
|
|
||||||
this.createCheckbox = this.modal.getByTestId(
|
|
||||||
'isCreator-create-checkbox'
|
|
||||||
).locator('input');
|
|
||||||
this.readCheckbox = this.modal.getByTestId(
|
|
||||||
'isCreator-read-checkbox'
|
|
||||||
).locator('input');
|
|
||||||
this.updateCheckbox = this.modal.getByTestId(
|
|
||||||
'isCreator-update-checkbox'
|
|
||||||
).locator('input');
|
|
||||||
this.deleteCheckbox = this.modal.getByTestId(
|
|
||||||
'isCreator-delete-checkbox'
|
|
||||||
).locator('input');
|
|
||||||
this.publishCheckbox = this.modal.getByTestId(
|
|
||||||
'isCreator-publish-checkbox'
|
|
||||||
).locator('input');
|
|
||||||
this.applyButton = this.modal.getByTestId('confirmation-confirm-button');
|
|
||||||
this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAvailableConditions () {
|
|
||||||
let conditions = {};
|
|
||||||
const actions = ['create', 'read', 'update', 'delete', 'publish'];
|
|
||||||
for (let action of actions) {
|
|
||||||
const locator = this[`${action}Checkbox`];
|
|
||||||
if (locator && await locator.count() > 0) {
|
|
||||||
conditions[action] = locator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conditions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async close () {
|
|
||||||
await this.page.click('body', {
|
|
||||||
position: { x: 10, y: 10 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,79 +0,0 @@
|
|||||||
const { AuthenticatedPage } = require('../authenticated-page');
|
|
||||||
const { DeleteRoleModal } = require('./delete-role-modal')
|
|
||||||
|
|
||||||
export class AdminRolesPage extends AuthenticatedPage {
|
|
||||||
screenshotPath = '/admin-roles';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
this.roleDrawerLink = page.getByTestId('roles-drawer-link');
|
|
||||||
this.createRoleButton = page.getByTestId('create-role');
|
|
||||||
this.deleteRoleModal = new DeleteRoleModal(page);
|
|
||||||
this.roleRow = page.getByTestId('role-row');
|
|
||||||
this.rolesLoader = page.getByTestId('roles-list-loader');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {boolean} isMobile - navigation on smaller devices requires the
|
|
||||||
* user to open up the drawer menu
|
|
||||||
*/
|
|
||||||
async navigateTo (isMobile=false) {
|
|
||||||
await this.profileMenuButton.click();
|
|
||||||
await this.adminMenuItem.click();
|
|
||||||
if (isMobile) {
|
|
||||||
await this.drawerMenuButton.click();
|
|
||||||
}
|
|
||||||
await this.roleDrawerLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
async getRoleRowByName (name) {
|
|
||||||
return this.roleRow.filter({
|
|
||||||
has: this.page.getByTestId('role-name').filter({
|
|
||||||
hasText: name
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async getRowData (row) {
|
|
||||||
return {
|
|
||||||
role: await row.getByTestId('role-name').textContent(),
|
|
||||||
description: await row.getByTestId('role-description').textContent(),
|
|
||||||
canEdit: await row.getByTestId(
|
|
||||||
'role-edit'
|
|
||||||
).isEnabled(),
|
|
||||||
canDelete: await row.getByTestId(
|
|
||||||
'role-delete'
|
|
||||||
).isEnabled()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async clickEditRole (row) {
|
|
||||||
await row.getByTestId('role-edit').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async clickDeleteRole (row) {
|
|
||||||
await row.getByTestId('role-delete').click();
|
|
||||||
return this.deleteRoleModal;
|
|
||||||
}
|
|
||||||
|
|
||||||
async editRole (subject) {
|
|
||||||
const row = await this.getRoleRowByName(subject);
|
|
||||||
await this.clickEditRole(row);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,131 +0,0 @@
|
|||||||
const { faker } = require('@faker-js/faker');
|
|
||||||
const { AuthenticatedPage } = require('../authenticated-page');
|
|
||||||
const { DeleteUserModal } = require('./delete-user-modal');
|
|
||||||
|
|
||||||
faker.seed(9001);
|
|
||||||
|
|
||||||
export class AdminUsersPage extends AuthenticatedPage {
|
|
||||||
screenshotPath = '/admin';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
*/
|
|
||||||
constructor (page) {
|
|
||||||
super(page);
|
|
||||||
this.createUserButton = page.getByTestId('create-user');
|
|
||||||
this.userRow = page.getByTestId('user-row');
|
|
||||||
this.deleteUserModal = new DeleteUserModal(page);
|
|
||||||
this.firstPageButton = page.getByTestId('first-page-button');
|
|
||||||
this.previousPageButton = page.getByTestId('previous-page-button');
|
|
||||||
this.nextPageButton = page.getByTestId('next-page-button');
|
|
||||||
this.lastPageButton = page.getByTestId('last-page-button');
|
|
||||||
this.usersLoader = page.getByTestId('users-list-loader');
|
|
||||||
}
|
|
||||||
|
|
||||||
async navigateTo () {
|
|
||||||
await this.profileMenuButton.click();
|
|
||||||
await this.adminMenuItem.click();
|
|
||||||
if (await this.usersLoader.isVisible()) {
|
|
||||||
await this.usersLoader.waitFor({
|
|
||||||
state: 'detached'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} email
|
|
||||||
*/
|
|
||||||
async getUserRowByEmail (email) {
|
|
||||||
return this.userRow.filter({
|
|
||||||
has: this.page.getByTestId('user-email').filter({
|
|
||||||
hasText: email
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async getRowData (row) {
|
|
||||||
return {
|
|
||||||
fullName: await row.getByTestId('user-full-name').textContent(),
|
|
||||||
email: await row.getByTestId('user-email').textContent(),
|
|
||||||
role: await row.getByTestId('user-role').textContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async clickEditUser (row) {
|
|
||||||
await row.getByTestId('user-edit').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Locator} row
|
|
||||||
*/
|
|
||||||
async clickDeleteUser (row) {
|
|
||||||
await row.getByTestId('delete-button').click();
|
|
||||||
return this.deleteUserModal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} email
|
|
||||||
* @returns {import('@playwright/test').Locator | null}
|
|
||||||
*/
|
|
||||||
async findUserPageWithEmail (email) {
|
|
||||||
if (await this.usersLoader.isVisible()) {
|
|
||||||
await this.usersLoader.waitFor({
|
|
||||||
state: 'detached'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// start at the first page
|
|
||||||
const firstPageDisabled = await this.firstPageButton.isDisabled();
|
|
||||||
if (!firstPageDisabled) {
|
|
||||||
await this.firstPageButton.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (await this.usersLoader.isVisible()) {
|
|
||||||
await this.usersLoader.waitFor({
|
|
||||||
state: 'detached'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const rowLocator = await this.getUserRowByEmail(email);
|
|
||||||
if ((await rowLocator.count()) === 1) {
|
|
||||||
return rowLocator;
|
|
||||||
}
|
|
||||||
if (await this.nextPageButton.isDisabled()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
await this.nextPageButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTotalRows () {
|
|
||||||
return await this.page.evaluate(() => {
|
|
||||||
const node = document.querySelector('[data-total-count]');
|
|
||||||
if (node) {
|
|
||||||
const count = Number(node.dataset.totalCount);
|
|
||||||
if (!isNaN(count)) {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRowsPerPage () {
|
|
||||||
return await this.page.evaluate(() => {
|
|
||||||
const node = document.querySelector('[data-rows-per-page]');
|
|
||||||
if (node) {
|
|
||||||
const count = Number(node.dataset.rowsPerPage);
|
|
||||||
if (!isNaN(count)) {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,9 +13,9 @@ export class ApplicationsModal extends BasePage {
|
|||||||
constructor (page) {
|
constructor (page) {
|
||||||
super(page);
|
super(page);
|
||||||
this.modal = page.getByTestId('add-app-connection-dialog');
|
this.modal = page.getByTestId('add-app-connection-dialog');
|
||||||
this.searchInput = this.modal.getByTestId('search-for-app-text-field');
|
this.searchInput = page.getByTestId('search-for-app-text-field');
|
||||||
this.appListItem = this.modal.getByTestId('app-list-item');
|
this.appListItem = page.getByTestId('app-list-item');
|
||||||
this.appLoader = this.modal.getByTestId('search-for-app-loader');
|
this.appLoader = page.getByTestId('search-for-app-loader');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -14,7 +14,6 @@ export class AuthenticatedPage extends BasePage {
|
|||||||
this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' });
|
this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' });
|
||||||
this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link');
|
this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link');
|
||||||
this.appBar = this.page.getByTestId('app-bar');
|
this.appBar = this.page.getByTestId('app-bar');
|
||||||
this.drawerMenuButton = this.page.getByTestId('drawer-menu-button');
|
|
||||||
this.goToDashboardButton = this.page.getByTestId('go-back-drawer-link');
|
this.goToDashboardButton = this.page.getByTestId('go-back-drawer-link');
|
||||||
this.typographyLogo = this.page.getByTestId('typography-logo');
|
this.typographyLogo = this.page.getByTestId('typography-logo');
|
||||||
this.customLogo = this.page.getByTestId('custom-logo');
|
this.customLogo = this.page.getByTestId('custom-logo');
|
||||||
|
@@ -1,11 +1,5 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {(
|
|
||||||
* 'default' | 'success' | 'warning' | 'error' | 'info'
|
|
||||||
* )} SnackbarVariant - Snackbar variant types in notistack/v3, see https://notistack.com/api-reference
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class BasePage {
|
export class BasePage {
|
||||||
screenshotPath = '/';
|
screenshotPath = '/';
|
||||||
|
|
||||||
@@ -14,53 +8,7 @@ export class BasePage {
|
|||||||
*/
|
*/
|
||||||
constructor(page) {
|
constructor(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
this.snackbar = page.locator('*[data-test^="snackbar"]');
|
this.snackbar = this.page.locator('#notistack-snackbar');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the latest snackbar message and extracts relevant data
|
|
||||||
* @param {string | undefined} testId
|
|
||||||
* @returns {(
|
|
||||||
* null | {
|
|
||||||
* variant: SnackbarVariant,
|
|
||||||
* text: string,
|
|
||||||
* dataset: { [key: string]: string }
|
|
||||||
* }
|
|
||||||
* )}
|
|
||||||
*/
|
|
||||||
async getSnackbarData (testId) {
|
|
||||||
if (!testId) {
|
|
||||||
testId = 'snackbar';
|
|
||||||
}
|
|
||||||
const snack = this.page.getByTestId(testId);
|
|
||||||
return {
|
|
||||||
variant: await snack.getAttribute('data-snackbar-variant'),
|
|
||||||
text: await snack.evaluate(node => node.innerText),
|
|
||||||
dataset: await snack.evaluate(node => {
|
|
||||||
function getChildren (n) {
|
|
||||||
return [n].concat(
|
|
||||||
...Array.from(n.children).map(c => getChildren(c))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const datasets = getChildren(node).map(
|
|
||||||
n => Object.assign({}, n.dataset)
|
|
||||||
);
|
|
||||||
return Object.assign({}, ...datasets);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes all snackbars, should be replaced later
|
|
||||||
*/
|
|
||||||
async closeSnackbar () {
|
|
||||||
const snackbars = await this.snackbar.all();
|
|
||||||
for (const snackbar of snackbars) {
|
|
||||||
await snackbar.click();
|
|
||||||
}
|
|
||||||
for (const snackbar of snackbars) {
|
|
||||||
await snackbar.waitFor({ state: 'detached' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickAway() {
|
async clickAway() {
|
||||||
|
@@ -5,7 +5,6 @@ const { ExecutionsPage } = require('./executions-page');
|
|||||||
const { FlowEditorPage } = require('./flow-editor-page');
|
const { FlowEditorPage } = require('./flow-editor-page');
|
||||||
const { UserInterfacePage } = require('./user-interface-page');
|
const { UserInterfacePage } = require('./user-interface-page');
|
||||||
const { LoginPage } = require('./login-page');
|
const { LoginPage } = require('./login-page');
|
||||||
const { adminFixtures } = require('./admin');
|
|
||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
page: async ({ page }, use) => {
|
page: async ({ page }, use) => {
|
||||||
@@ -32,7 +31,6 @@ exports.test = test.extend({
|
|||||||
userInterfacePage: async ({ page }, use) => {
|
userInterfacePage: async ({ page }, use) => {
|
||||||
await use(new UserInterfacePage(page));
|
await use(new UserInterfacePage(page));
|
||||||
},
|
},
|
||||||
...adminFixtures
|
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.publicTest = test.extend({
|
exports.publicTest = test.extend({
|
||||||
|
@@ -1,19 +1,9 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
const { BasePage } = require('./base-page');
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
export class LoginPage extends BasePage {
|
export class LoginPage extends BasePage {
|
||||||
path = '/login';
|
path = '/login';
|
||||||
static defaultEmail = process.env.LOGIN_EMAIL;
|
|
||||||
static defaultPassword = process.env.LOGIN_PASSWORD;
|
|
||||||
|
|
||||||
static setDefaultLogin (email, password) {
|
|
||||||
this.defaultEmail = email;
|
|
||||||
this.defaultPassword = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
static resetDefaultLogin () {
|
|
||||||
this.defaultEmail = process.env.LOGIN_EMAIL;
|
|
||||||
this.defaultPassword = process.env.LOGIN_PASSWORD;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
@@ -32,8 +22,8 @@ export class LoginPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async login(
|
async login(
|
||||||
email = LoginPage.defaultEmail,
|
email = process.env.LOGIN_EMAIL,
|
||||||
password = LoginPage.defaultPassword
|
password = process.env.LOGIN_PASSWORD
|
||||||
) {
|
) {
|
||||||
await this.page.goto(this.path);
|
await this.page.goto(this.path);
|
||||||
await this.emailTextField.fill(email);
|
await this.emailTextField.fill(email);
|
||||||
|
@@ -24,17 +24,10 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^8.2.0",
|
|
||||||
"@playwright/test": "^1.36.2"
|
"@playwright/test": "^1.36.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
|
||||||
"@typescript-eslint/parser": "^5.9.1",
|
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint": "^8.13.0",
|
"micro": "^10.0.1"
|
||||||
"eslint-config-prettier": "^8.3.0",
|
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
|
||||||
"micro": "^10.0.1",
|
|
||||||
"prettier": "^2.5.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,458 +0,0 @@
|
|||||||
const { test, expect } = require('../../fixtures/index');
|
|
||||||
const { LoginPage } = require('../../fixtures/login-page');
|
|
||||||
|
|
||||||
test.describe('Role management page', () => {
|
|
||||||
test.skip('Admin role is not deletable', async ({ adminRolesPage }) => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
const adminRow = await adminRolesPage.getRoleRowByName('Admin');
|
|
||||||
const rowCount = await adminRow.count();
|
|
||||||
await expect(rowCount).toBe(1);
|
|
||||||
const data = await adminRolesPage.getRowData(adminRow);
|
|
||||||
await expect(data.role).toBe('Admin');
|
|
||||||
await expect(data.canEdit).toBe(true);
|
|
||||||
await expect(data.canDelete).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can create, edit, and delete a role', async ({
|
|
||||||
adminCreateRolePage,
|
|
||||||
adminEditRolePage,
|
|
||||||
adminRolesPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await test.step('Create a new role', async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
await adminCreateRolePage.nameInput.fill('Create Edit Test');
|
|
||||||
await adminCreateRolePage.descriptionInput.fill('Test description');
|
|
||||||
await adminCreateRolePage.createButton.click();
|
|
||||||
await adminCreateRolePage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
|
||||||
'snackbar-create-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
let roleRow = await test.step(
|
|
||||||
'Make sure role data is correct',
|
|
||||||
async () => {
|
|
||||||
const roleRow = await adminRolesPage.getRoleRowByName(
|
|
||||||
'Create Edit Test'
|
|
||||||
);
|
|
||||||
const rowCount = await roleRow.count();
|
|
||||||
await expect(rowCount).toBe(1);
|
|
||||||
const roleData = await adminRolesPage.getRowData(roleRow);
|
|
||||||
await expect(roleData.role).toBe('Create Edit Test');
|
|
||||||
await expect(roleData.description).toBe('Test description');
|
|
||||||
await expect(roleData.canEdit).toBe(true);
|
|
||||||
await expect(roleData.canDelete).toBe(true);
|
|
||||||
return roleRow;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step('Edit the role', async () => {
|
|
||||||
await adminRolesPage.clickEditRole(roleRow);
|
|
||||||
await adminEditRolePage.nameInput.fill('Create Update Test');
|
|
||||||
await adminEditRolePage.descriptionInput.fill('Update test description');
|
|
||||||
await adminEditRolePage.updateButton.click();
|
|
||||||
await adminEditRolePage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminEditRolePage.getSnackbarData(
|
|
||||||
'snackbar-edit-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminEditRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
roleRow = await test.step(
|
|
||||||
'Make sure changes reflected on roles page',
|
|
||||||
async () => {
|
|
||||||
const roleRow = await adminRolesPage.getRoleRowByName(
|
|
||||||
'Create Update Test'
|
|
||||||
);
|
|
||||||
const rowCount = await roleRow.count();
|
|
||||||
await expect(rowCount).toBe(1);
|
|
||||||
const roleData = await adminRolesPage.getRowData(roleRow);
|
|
||||||
await expect(roleData.role).toBe('Create Update Test');
|
|
||||||
await expect(roleData.description).toBe('Update test description');
|
|
||||||
await expect(roleData.canEdit).toBe(true);
|
|
||||||
await expect(roleData.canDelete).toBe(true);
|
|
||||||
return roleRow;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step('Delete the role', async () => {
|
|
||||||
await adminRolesPage.clickDeleteRole(roleRow);
|
|
||||||
const deleteModal = adminRolesPage.deleteRoleModal;
|
|
||||||
await deleteModal.modal.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
await deleteModal.deleteButton.click();
|
|
||||||
await adminRolesPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminRolesPage.getSnackbarData(
|
|
||||||
'snackbar-delete-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminRolesPage.closeSnackbar();
|
|
||||||
await deleteModal.modal.waitFor({
|
|
||||||
state: 'detached',
|
|
||||||
});
|
|
||||||
const rowCount = await roleRow.count();
|
|
||||||
await expect(rowCount).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// This test breaks right now
|
|
||||||
test.skip('Make sure create/edit role page is scrollable', async ({
|
|
||||||
adminRolesPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const initViewportSize = page.viewportSize;
|
|
||||||
await page.setViewportSize({
|
|
||||||
width: 800,
|
|
||||||
height: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Ensure create role page is scrollable', async () => {
|
|
||||||
await adminRolesPage.navigateTo(true);
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
|
|
||||||
const initScrollTop = await page.evaluate(() => {
|
|
||||||
return document.documentElement.scrollTop;
|
|
||||||
});
|
|
||||||
await page.mouse.move(400, 100);
|
|
||||||
await page.mouse.click(400, 100);
|
|
||||||
await page.mouse.wheel(200, 0);
|
|
||||||
const updatedScrollTop = await page.evaluate(() => {
|
|
||||||
return document.documentElement.scrollTop;
|
|
||||||
});
|
|
||||||
await expect(initScrollTop).not.toBe(updatedScrollTop);
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Ensure edit role page is scrollable', async () => {
|
|
||||||
await adminRolesPage.navigateTo(true);
|
|
||||||
const adminRow = await adminRolesPage.getRoleRowByName('Admin');
|
|
||||||
await adminRolesPage.clickEditRole(adminRow);
|
|
||||||
|
|
||||||
const initScrollTop = await page.evaluate(() => {
|
|
||||||
return document.documentElement.scrollTop;
|
|
||||||
});
|
|
||||||
await page.mouse.move(400, 100);
|
|
||||||
await page.mouse.wheel(200, 0);
|
|
||||||
const updatedScrollTop = await page.evaluate(() => {
|
|
||||||
return document.documentElement.scrollTop;
|
|
||||||
});
|
|
||||||
await expect(initScrollTop).not.toBe(updatedScrollTop);
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Reset viewport', async () => {
|
|
||||||
await page.setViewportSize(initViewportSize);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Cannot delete a role with a user attached to it', async ({
|
|
||||||
adminCreateRolePage,
|
|
||||||
adminRolesPage,
|
|
||||||
adminUsersPage,
|
|
||||||
adminCreateUserPage,
|
|
||||||
adminEditUserPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await test.step('Create a new role', async () => {
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
await adminCreateRolePage.nameInput.fill('Delete Role');
|
|
||||||
await adminCreateRolePage.createButton.click();
|
|
||||||
await adminCreateRolePage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
|
||||||
'snackbar-create-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
await test.step(
|
|
||||||
'Create a new user with the "Delete Role" role',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill('User Role Test');
|
|
||||||
await adminCreateUserPage.emailInput.fill(
|
|
||||||
'user-role-test@automatisch.io'
|
|
||||||
);
|
|
||||||
await adminCreateUserPage.passwordInput.fill('sample');
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page
|
|
||||||
.getByRole('option', { name: 'Delete Role' })
|
|
||||||
.click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
await adminUsersPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await test.step(
|
|
||||||
'Try to delete "Delete Role" role when new user has it',
|
|
||||||
async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
const row = await adminRolesPage.getRoleRowByName('Delete Role');
|
|
||||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
await adminRolesPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminRolesPage.getSnackbarData('snackbar-error');
|
|
||||||
await expect(snackbar.variant).toBe('error');
|
|
||||||
await adminRolesPage.closeSnackbar();
|
|
||||||
await modal.close();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await test.step('Change the role the user has', async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
await adminUsersPage.usersLoader.waitFor({
|
|
||||||
state: 'detached',
|
|
||||||
});
|
|
||||||
const row = await adminUsersPage.findUserPageWithEmail(
|
|
||||||
'user-role-test@automatisch.io'
|
|
||||||
);
|
|
||||||
await adminUsersPage.clickEditUser(row);
|
|
||||||
await adminEditUserPage.roleInput.click();
|
|
||||||
await adminEditUserPage.page
|
|
||||||
.getByRole('option', { name: 'Admin' })
|
|
||||||
.click();
|
|
||||||
await adminEditUserPage.updateButton.click();
|
|
||||||
await adminEditUserPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminEditUserPage.getSnackbarData(
|
|
||||||
'snackbar-edit-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminEditUserPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
await test.step('Delete the original role', async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
const row = await adminRolesPage.getRoleRowByName('Delete Role');
|
|
||||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
|
||||||
await expect(modal.modal).toBeVisible();
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
await adminRolesPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminRolesPage.getSnackbarData(
|
|
||||||
'snackbar-delete-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminRolesPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Deleting a role after deleting a user with that role', async ({
|
|
||||||
adminCreateRolePage,
|
|
||||||
adminRolesPage,
|
|
||||||
adminUsersPage,
|
|
||||||
adminCreateUserPage,
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await test.step('Create a new role', async () => {
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
await adminCreateRolePage.nameInput.fill('Cannot Delete Role');
|
|
||||||
await adminCreateRolePage.createButton.click();
|
|
||||||
await adminCreateRolePage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
|
||||||
'snackbar-create-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
await test.step('Create a new user with this role', async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill('User Delete Role Test');
|
|
||||||
await adminCreateUserPage.emailInput.fill(
|
|
||||||
'user-delete-role-test@automatisch.io'
|
|
||||||
);
|
|
||||||
await adminCreateUserPage.passwordInput.fill('sample');
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page
|
|
||||||
.getByRole('option', { name: 'Cannot Delete Role' })
|
|
||||||
.click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
await adminCreateUserPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateUserPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateUserPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
await test.step('Delete this user', async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
const row = await adminUsersPage.findUserPageWithEmail(
|
|
||||||
'user-delete-role-test@automatisch.io'
|
|
||||||
);
|
|
||||||
const modal = await adminUsersPage.clickDeleteUser(row);
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
await adminUsersPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-delete-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
await test.step('Try deleting this role', async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
const row = await adminRolesPage.getRoleRowByName('Cannot Delete Role');
|
|
||||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
await adminRolesPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
* TODO: await snackbar - make assertions based on product
|
|
||||||
* decisions
|
|
||||||
const snackbar = await adminRolesPage.getSnackbarData();
|
|
||||||
await expect(snackbar.variant).toBe('...');
|
|
||||||
*/
|
|
||||||
await adminRolesPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Accessibility of role management page', async ({
|
|
||||||
page,
|
|
||||||
adminUsersPage,
|
|
||||||
adminCreateUserPage,
|
|
||||||
adminEditUserPage,
|
|
||||||
adminRolesPage,
|
|
||||||
adminCreateRolePage,
|
|
||||||
}) => {
|
|
||||||
test.slow();
|
|
||||||
await test.step('Create the basic test role', async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
await adminCreateRolePage.nameInput.fill('Basic Test');
|
|
||||||
await adminCreateRolePage.createButton.click();
|
|
||||||
await adminCreateRolePage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
|
||||||
'snackbar-create-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Create a new user with the basic role', async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill('Role Test');
|
|
||||||
await adminCreateUserPage.emailInput.fill('basic-role-test@automatisch.io');
|
|
||||||
await adminCreateUserPage.passwordInput.fill('sample');
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page
|
|
||||||
.getByRole('option', { name: 'Basic Test' })
|
|
||||||
.click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
await adminCreateUserPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminCreateUserPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminCreateRolePage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Logout and login to the basic role user', async () => {
|
|
||||||
await page.getByTestId('profile-menu-button').click();
|
|
||||||
await page.getByTestId('logout-item').click();
|
|
||||||
// await page.reload({ waitUntil: 'networkidle' });
|
|
||||||
const loginPage = new LoginPage(page);
|
|
||||||
await loginPage.login('basic-role-test@automatisch.io', 'sample');
|
|
||||||
await expect(loginPage.loginButton).not.toBeVisible();
|
|
||||||
await expect(page).toHaveURL('/flows');
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Navigate to the admin settings page and make sure it is blank',
|
|
||||||
async () => {
|
|
||||||
const pageUrl = new URL(page.url());
|
|
||||||
const url = `${pageUrl.origin}/admin-settings/users`;
|
|
||||||
await page.goto(url);
|
|
||||||
await page.waitForTimeout(750);
|
|
||||||
const isUnmounted = await page.evaluate(() => {
|
|
||||||
const root = document.querySelector('#root');
|
|
||||||
if (root) {
|
|
||||||
return root.children.length === 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
await expect(isUnmounted).toBe(true);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step('Log back into the admin account', async () => {
|
|
||||||
await page.goto('/');
|
|
||||||
await page.getByTestId('profile-menu-button').click();
|
|
||||||
await page.getByTestId('logout-item').click();
|
|
||||||
const loginPage = new LoginPage(page);
|
|
||||||
await loginPage.login();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Move the user off the role', async () => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
const row = await adminUsersPage.findUserPageWithEmail(
|
|
||||||
'basic-role-test@automatisch.io'
|
|
||||||
);
|
|
||||||
await adminUsersPage.clickEditUser(row);
|
|
||||||
await adminEditUserPage.roleInput.click();
|
|
||||||
await adminEditUserPage.page.getByRole('option', { name: 'Admin' }).click();
|
|
||||||
await adminEditUserPage.updateButton.click();
|
|
||||||
await adminEditUserPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
await adminEditUserPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step('Delete the role', async () => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
const roleRow = await adminRolesPage.getRoleRowByName('Basic Test');
|
|
||||||
await adminRolesPage.clickDeleteRole(roleRow);
|
|
||||||
const deleteModal = adminRolesPage.deleteRoleModal;
|
|
||||||
await deleteModal.modal.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
await deleteModal.deleteButton.click();
|
|
||||||
await adminRolesPage.snackbar.waitFor({
|
|
||||||
state: 'attached',
|
|
||||||
});
|
|
||||||
const snackbar = await adminRolesPage.getSnackbarData(
|
|
||||||
'snackbar-delete-role-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminRolesPage.closeSnackbar();
|
|
||||||
await deleteModal.modal.waitFor({
|
|
||||||
state: 'detached',
|
|
||||||
});
|
|
||||||
const rowCount = await roleRow.count();
|
|
||||||
await expect(rowCount).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,287 +0,0 @@
|
|||||||
const { test, expect } = require('../../fixtures/index');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: Make sure to delete all users generated between test runs,
|
|
||||||
* otherwise tests will fail since users are only *soft*-deleted
|
|
||||||
*/
|
|
||||||
test.describe('User management page', () => {
|
|
||||||
|
|
||||||
test.beforeEach(async ({ adminUsersPage }) => {
|
|
||||||
await adminUsersPage.navigateTo();
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'User creation and deletion process',
|
|
||||||
async ({ adminCreateUserPage, adminEditUserPage, adminUsersPage }) => {
|
|
||||||
adminCreateUserPage.seed(9000);
|
|
||||||
const user = adminCreateUserPage.generateUser();
|
|
||||||
await adminUsersPage.usersLoader.waitFor({
|
|
||||||
state: 'detached' /* Note: state: 'visible' introduces flakiness
|
|
||||||
because visibility: hidden is used as part of the state transition in
|
|
||||||
notistack, see
|
|
||||||
https://github.com/iamhosseindhv/notistack/blob/122f47057eb7ce5a1abfe923316cf8475303e99a/src/transitions/Collapse/Collapse.tsx#L110
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
await test.step(
|
|
||||||
'Create a user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(user.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(user.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(user.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await test.step(
|
|
||||||
'Check the user exists with the expected properties',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
|
||||||
const userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
|
||||||
const data = await adminUsersPage.getRowData(userRow);
|
|
||||||
await expect(data.email).toBe(user.email);
|
|
||||||
await expect(data.fullName).toBe(user.fullName);
|
|
||||||
await expect(data.role).toBe('Admin');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await test.step(
|
|
||||||
'Edit user info and make sure the edit works correctly',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
|
||||||
|
|
||||||
let userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
|
||||||
await adminUsersPage.clickEditUser(userRow);
|
|
||||||
const newUserInfo = adminEditUserPage.generateUser();
|
|
||||||
await adminEditUserPage.fullNameInput.fill(newUserInfo.fullName);
|
|
||||||
await adminEditUserPage.updateButton.click();
|
|
||||||
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-edit-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
|
|
||||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
|
||||||
userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
|
||||||
const rowData = await adminUsersPage.getRowData(userRow);
|
|
||||||
await expect(rowData.fullName).toBe(newUserInfo.fullName);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await test.step(
|
|
||||||
'Delete user and check the page confirms this deletion',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
|
||||||
const userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
|
||||||
await adminUsersPage.clickDeleteUser(userRow);
|
|
||||||
const modal = adminUsersPage.deleteUserModal;
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-delete-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
await expect(userRow).not.toBeVisible(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Creating a user which has been deleted',
|
|
||||||
async ({ adminCreateUserPage, adminUsersPage }) => {
|
|
||||||
adminCreateUserPage.seed(9100);
|
|
||||||
const testUser = adminCreateUserPage.generateUser();
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Create the test user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(testUser.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Delete the created user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
|
||||||
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
|
||||||
await adminUsersPage.clickDeleteUser(userRow);
|
|
||||||
const modal = adminUsersPage.deleteUserModal;
|
|
||||||
await modal.deleteButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-delete-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar).not.toBeNull();
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
await expect(userRow).not.toBeVisible(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Create the user again',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(testUser.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
await adminUsersPage.snackbar.waitFor({
|
|
||||||
state: 'attached'
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
TODO: assert snackbar behavior after deciding what should
|
|
||||||
happen here, i.e. if this should create a new user, stay the
|
|
||||||
same, un-delete the user, or something else
|
|
||||||
*/
|
|
||||||
// await adminUsersPage.getSnackbarData('snackbar-error');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Creating a user which already exists',
|
|
||||||
async ({ adminCreateUserPage, adminUsersPage, page }) => {
|
|
||||||
adminCreateUserPage.seed(9200);
|
|
||||||
const testUser = adminCreateUserPage.generateUser();
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Create the test user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(testUser.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Create the user again',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(testUser.password);
|
|
||||||
const createUserPageUrl = page.url();
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
|
|
||||||
await expect(page.url()).toBe(createUserPageUrl);
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
|
||||||
await expect(snackbar.variant).toBe('error');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Editing a user to have the same email as another user should not be allowed',
|
|
||||||
async ({
|
|
||||||
adminCreateUserPage, adminEditUserPage, adminUsersPage, page
|
|
||||||
}) => {
|
|
||||||
adminCreateUserPage.seed(9300);
|
|
||||||
const user1 = adminCreateUserPage.generateUser();
|
|
||||||
const user2 = adminCreateUserPage.generateUser();
|
|
||||||
await test.step(
|
|
||||||
'Create the first user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(user1.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(user1.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(user1.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Create the second user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.createUserButton.click();
|
|
||||||
await adminCreateUserPage.fullNameInput.fill(user2.fullName);
|
|
||||||
await adminCreateUserPage.emailInput.fill(user2.email);
|
|
||||||
await adminCreateUserPage.passwordInput.fill(user2.password);
|
|
||||||
await adminCreateUserPage.roleInput.click();
|
|
||||||
await adminCreateUserPage.page.getByRole(
|
|
||||||
'option', { name: 'Admin' }
|
|
||||||
).click();
|
|
||||||
await adminCreateUserPage.createButton.click();
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-create-user-success'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('success');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Try editing the second user to have the email of the first user',
|
|
||||||
async () => {
|
|
||||||
await adminUsersPage.findUserPageWithEmail(user2.email);
|
|
||||||
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
|
|
||||||
await adminUsersPage.clickEditUser(userRow);
|
|
||||||
|
|
||||||
await adminEditUserPage.emailInput.fill(user1.email);
|
|
||||||
const editPageUrl = page.url();
|
|
||||||
await adminEditUserPage.updateButton.click();
|
|
||||||
|
|
||||||
const snackbar = await adminUsersPage.getSnackbarData(
|
|
||||||
'snackbar-error'
|
|
||||||
);
|
|
||||||
await expect(snackbar.variant).toBe('error');
|
|
||||||
await adminUsersPage.closeSnackbar();
|
|
||||||
await expect(page.url()).toBe(editPageUrl);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
@@ -1,69 +0,0 @@
|
|||||||
const { test, expect } = require('../../fixtures/index');
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Role permissions conform with role conditions ',
|
|
||||||
async({ adminRolesPage, adminCreateRolePage }) => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
|
|
||||||
/*
|
|
||||||
example config: {
|
|
||||||
action: 'read',
|
|
||||||
subject: 'connection',
|
|
||||||
row: page.getByTestId('connection-permission-row'),
|
|
||||||
locator: row.getByTestId('read-checkbox')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const permissionConfigs =
|
|
||||||
await adminCreateRolePage.getPermissionConfigs();
|
|
||||||
|
|
||||||
await test.step(
|
|
||||||
'Iterate over each permission config and make sure role conditions conform',
|
|
||||||
async () => {
|
|
||||||
for (let config of permissionConfigs) {
|
|
||||||
await config.locator.click();
|
|
||||||
await adminCreateRolePage.clickPermissionSettings(config.row);
|
|
||||||
const modal = adminCreateRolePage.getRoleConditionsModal(
|
|
||||||
config.subject
|
|
||||||
);
|
|
||||||
await expect(modal.modal).toBeVisible();
|
|
||||||
const conditions = await modal.getAvailableConditions();
|
|
||||||
for (let conditionAction of Object.keys(conditions)) {
|
|
||||||
if (conditionAction === config.action) {
|
|
||||||
await expect(conditions[conditionAction]).not.toBeDisabled();
|
|
||||||
} else {
|
|
||||||
await expect(conditions[conditionAction]).toBeDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await modal.close();
|
|
||||||
await config.locator.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Default role permissions conforms with role conditions',
|
|
||||||
async({ adminRolesPage, adminCreateRolePage }) => {
|
|
||||||
await adminRolesPage.navigateTo();
|
|
||||||
await adminRolesPage.createRoleButton.click();
|
|
||||||
|
|
||||||
const subjects = ['Connection', 'Execution', 'Flow'];
|
|
||||||
for (let subject of subjects) {
|
|
||||||
const row = adminCreateRolePage.getSubjectRow(subject)
|
|
||||||
const modal = adminCreateRolePage.getRoleConditionsModal(subject);
|
|
||||||
await adminCreateRolePage.clickPermissionSettings(row);
|
|
||||||
await expect(modal.modal).toBeVisible();
|
|
||||||
const availableConditions = await modal.getAvailableConditions();
|
|
||||||
const conditions = ['create', 'read', 'update', 'delete', 'publish'];
|
|
||||||
for (let condition of conditions) {
|
|
||||||
if (availableConditions[condition]) {
|
|
||||||
await expect(availableConditions[condition]).toBeDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
@@ -9,7 +9,7 @@ test('Github OAuth integration', async ({ page, applicationsPage }) => {
|
|||||||
await page.waitForURL('/apps');
|
await page.waitForURL('/apps');
|
||||||
}
|
}
|
||||||
const connectionModal = await applicationsPage.openAddConnectionModal();
|
const connectionModal = await applicationsPage.openAddConnectionModal();
|
||||||
await expect(connectionModal.modal).toBeVisible();
|
expect(connectionModal.modal).toBeVisible();
|
||||||
return await connectionModal.selectLink('github');
|
return await connectionModal.selectLink('github');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -18,7 +18,7 @@ test('Github OAuth integration', async ({ page, applicationsPage }) => {
|
|||||||
'Ensure the github connection modal is visible',
|
'Ensure the github connection modal is visible',
|
||||||
async () => {
|
async () => {
|
||||||
const connectionModal = githubConnectionPage.addConnectionModal;
|
const connectionModal = githubConnectionPage.addConnectionModal;
|
||||||
await expect(connectionModal.modal).toBeVisible();
|
expect(connectionModal.modal).toBeVisible();
|
||||||
return connectionModal;
|
return connectionModal;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -35,9 +35,9 @@ test('Github OAuth integration', async ({ page, applicationsPage }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await test.step('Ensure github popup is not a 404', async () => {
|
await test.step('Ensure github popup is not a 404', async () => {
|
||||||
// await expect(githubPopup).toBeVisible();
|
// expect(githubPopup).toBeVisible();
|
||||||
const title = await githubPopup.title();
|
const title = await githubPopup.title();
|
||||||
await expect(title).not.toMatch(/^Page not found/);
|
expect(title).not.toMatch(/^Page not found/);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Skip these in CI
|
/* Skip these in CI
|
||||||
|
@@ -74,12 +74,7 @@ export default function AddNewAppConnection(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog open={true} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
open={true}
|
|
||||||
onClose={onClose}
|
|
||||||
maxWidth="sm"
|
|
||||||
fullWidth
|
|
||||||
data-test="add-app-connection-dialog">
|
|
||||||
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
||||||
|
|
||||||
<Box px={3}>
|
<Box px={3}>
|
||||||
|
@@ -6,13 +6,13 @@ import Paper from '@mui/material/Paper';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
||||||
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
||||||
|
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
import { Switch } from './style';
|
import { Switch } from './style';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
|
||||||
|
|
||||||
type AdminApplicationSettingsProps = {
|
type AdminApplicationSettingsProps = {
|
||||||
appKey: string;
|
appKey: string;
|
||||||
@@ -36,7 +36,7 @@ function AdminApplicationSettings(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const handleSubmit = async (values: any) => {
|
const handleSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -55,9 +55,6 @@ function AdminApplicationSettings(
|
|||||||
}
|
}
|
||||||
enqueueSnackbar(formatMessage('adminAppsSettings.successfullySaved'), {
|
enqueueSnackbar(formatMessage('adminAppsSettings.successfullySaved'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-save-admin-apps-settings-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed while saving!');
|
throw new Error('Failed while saving!');
|
||||||
|
@@ -15,12 +15,7 @@ const ApolloProvider = (props: ApolloProviderProps): React.ReactElement => {
|
|||||||
|
|
||||||
const onError = React.useCallback(
|
const onError = React.useCallback(
|
||||||
(message) => {
|
(message) => {
|
||||||
enqueueSnackbar(message, {
|
enqueueSnackbar(message, { variant: 'error' });
|
||||||
variant: 'error',
|
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-error'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[enqueueSnackbar]
|
[enqueueSnackbar]
|
||||||
);
|
);
|
||||||
|
@@ -56,7 +56,6 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
|
|||||||
aria-label="open drawer"
|
aria-label="open drawer"
|
||||||
onClick={drawerOpen ? onDrawerClose : onDrawerOpen}
|
onClick={drawerOpen ? onDrawerClose : onDrawerOpen}
|
||||||
sx={{ mr: 2 }}
|
sx={{ mr: 2 }}
|
||||||
data-test="drawer-menu-button"
|
|
||||||
>
|
>
|
||||||
{drawerOpen && matchSmallScreens ? <MenuOpenIcon /> : <MenuIcon />}
|
{drawerOpen && matchSmallScreens ? <MenuOpenIcon /> : <MenuIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -82,9 +82,6 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
|||||||
|
|
||||||
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
|
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-delete-connection-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else if (action.type === 'test') {
|
} else if (action.type === 'test') {
|
||||||
setVerificationVisible(true);
|
setVerificationVisible(true);
|
||||||
|
@@ -21,7 +21,6 @@ export default function ConditionalIconButton(props: any): React.ReactElement {
|
|||||||
component={buttonProps.component}
|
component={buttonProps.component}
|
||||||
to={buttonProps.to}
|
to={buttonProps.to}
|
||||||
disabled={buttonProps.disabled}
|
disabled={buttonProps.disabled}
|
||||||
data-test={buttonProps['data-test']}
|
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -14,7 +14,6 @@ type ConfirmationDialogProps = {
|
|||||||
cancelButtonChildren: React.ReactNode;
|
cancelButtonChildren: React.ReactNode;
|
||||||
confirmButtionChildren: React.ReactNode;
|
confirmButtionChildren: React.ReactNode;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
'data-test'?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ConfirmationDialog(props: ConfirmationDialogProps) {
|
export default function ConfirmationDialog(props: ConfirmationDialogProps) {
|
||||||
@@ -27,9 +26,9 @@ export default function ConfirmationDialog(props: ConfirmationDialogProps) {
|
|||||||
confirmButtionChildren,
|
confirmButtionChildren,
|
||||||
open = true,
|
open = true,
|
||||||
} = props;
|
} = props;
|
||||||
const dataTest = props['data-test'];
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} data-test={dataTest}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
{title && (
|
{title && (
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{title}
|
{title}
|
||||||
@@ -45,16 +44,11 @@ export default function ConfirmationDialog(props: ConfirmationDialogProps) {
|
|||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
{(cancelButtonChildren && onClose) && (
|
{(cancelButtonChildren && onClose) && (
|
||||||
<Button
|
<Button onClick={onClose}>{cancelButtonChildren}</Button>
|
||||||
onClick={onClose}
|
|
||||||
data-test="confirmation-cancel-button">{cancelButtonChildren}</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(confirmButtionChildren && onConfirm) && (
|
{(confirmButtionChildren && onConfirm) && (
|
||||||
<Button
|
<Button onClick={onConfirm} color="error">
|
||||||
onClick={onConfirm}
|
|
||||||
color="error"
|
|
||||||
data-test="confirmation-confirm-button">
|
|
||||||
{confirmButtionChildren}
|
{confirmButtionChildren}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@@ -5,7 +5,6 @@ import Checkbox, { CheckboxProps } from '@mui/material/Checkbox';
|
|||||||
type ControlledCheckboxProps = {
|
type ControlledCheckboxProps = {
|
||||||
name: string;
|
name: string;
|
||||||
defaultValue?: boolean;
|
defaultValue?: boolean;
|
||||||
dataTest?: string;
|
|
||||||
} & Omit<CheckboxProps, 'defaultValue'>;
|
} & Omit<CheckboxProps, 'defaultValue'>;
|
||||||
|
|
||||||
export default function ControlledCheckbox(
|
export default function ControlledCheckbox(
|
||||||
@@ -19,7 +18,6 @@ export default function ControlledCheckbox(
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
dataTest,
|
|
||||||
...checkboxProps
|
...checkboxProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -55,7 +53,6 @@ export default function ControlledCheckbox(
|
|||||||
onBlur?.(...args);
|
onBlur?.(...args);
|
||||||
}}
|
}}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
data-test={dataTest}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@@ -31,9 +31,6 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
|||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
enqueueSnackbar(formatMessage('deleteRoleButton.successfullyDeleted'), {
|
enqueueSnackbar(formatMessage('deleteRoleButton.successfullyDeleted'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-delete-role-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed while deleting!');
|
throw new Error('Failed while deleting!');
|
||||||
@@ -48,7 +45,6 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
|||||||
disabled={!allowed || disabled}
|
disabled={!allowed || disabled}
|
||||||
onClick={() => setShowConfirmation(true)}
|
onClick={() => setShowConfirmation(true)}
|
||||||
size="small"
|
size="small"
|
||||||
data-test="role-delete"
|
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -63,7 +59,6 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
|||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
cancelButtonChildren={formatMessage('deleteRoleButton.cancel')}
|
cancelButtonChildren={formatMessage('deleteRoleButton.cancel')}
|
||||||
confirmButtionChildren={formatMessage('deleteRoleButton.confirm')}
|
confirmButtionChildren={formatMessage('deleteRoleButton.confirm')}
|
||||||
data-test="delete-role-modal"
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -29,9 +29,6 @@ export default function DeleteUserButton(props: DeleteUserButtonProps) {
|
|||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-delete-user-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed while deleting!');
|
throw new Error('Failed while deleting!');
|
||||||
@@ -40,7 +37,7 @@ export default function DeleteUserButton(props: DeleteUserButtonProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton data-test="delete-button" onClick={() => setShowConfirmation(true)} size="small">
|
<IconButton onClick={() => setShowConfirmation(true)} size="small">
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
@@ -52,7 +49,6 @@ export default function DeleteUserButton(props: DeleteUserButtonProps) {
|
|||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
cancelButtonChildren={formatMessage('deleteUserButton.cancel')}
|
cancelButtonChildren={formatMessage('deleteUserButton.cancel')}
|
||||||
confirmButtionChildren={formatMessage('deleteUserButton.confirm')}
|
confirmButtionChildren={formatMessage('deleteUserButton.confirm')}
|
||||||
data-test="delete-user-modal"
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -39,15 +39,11 @@ function ExecutionId(props: Pick<IExecution, 'id'>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ExecutionDate(props: Pick<IExecution, 'createdAt'>) {
|
function ExecutionDate(props: Pick<IExecution, 'createdAt'>) {
|
||||||
const createdAt = DateTime.fromMillis(
|
const createdAt = DateTime.fromMillis(parseInt(props.createdAt, 10));
|
||||||
parseInt(props.createdAt as string, 10)
|
|
||||||
);
|
|
||||||
const relativeCreatedAt = createdAt.toRelative();
|
const relativeCreatedAt = createdAt.toRelative();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}>
|
||||||
title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
|
||||||
>
|
|
||||||
<Typography variant="body1" gutterBottom>
|
<Typography variant="body1" gutterBottom>
|
||||||
{relativeCreatedAt}
|
{relativeCreatedAt}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@@ -23,10 +23,8 @@ export default function ExecutionRow(
|
|||||||
const { execution } = props;
|
const { execution } = props;
|
||||||
const { flow } = execution;
|
const { flow } = execution;
|
||||||
|
|
||||||
const createdAt = DateTime.fromMillis(
|
const updatedAt = DateTime.fromMillis(parseInt(execution.updatedAt, 10));
|
||||||
parseInt(execution.createdAt as string, 10)
|
const relativeUpdatedAt = updatedAt.toRelative();
|
||||||
);
|
|
||||||
const relativeCreatedAt = createdAt.toRelative();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={URLS.EXECUTION(execution.id)} data-test="execution-row">
|
<Link to={URLS.EXECUTION(execution.id)} data-test="execution-row">
|
||||||
@@ -43,8 +41,8 @@ export default function ExecutionRow(
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="caption" noWrap>
|
<Typography variant="caption" noWrap>
|
||||||
{formatMessage('execution.createdAt', {
|
{formatMessage('execution.updatedAt', {
|
||||||
datetime: relativeCreatedAt,
|
datetime: relativeUpdatedAt,
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Title>
|
</Title>
|
||||||
|
@@ -36,9 +36,6 @@ export default function ContextMenu(
|
|||||||
|
|
||||||
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
|
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-duplicate-flow-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
@@ -65,8 +65,8 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
|
|||||||
setAnchorEl(contextButtonRef.current);
|
setAnchorEl(contextButtonRef.current);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdAt = DateTime.fromMillis(parseInt(flow.createdAt as string, 10));
|
const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10));
|
||||||
const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt as string, 10));
|
const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10));
|
||||||
const isUpdated = updatedAt > createdAt;
|
const isUpdated = updatedAt > createdAt;
|
||||||
const relativeCreatedAt = createdAt.toRelative();
|
const relativeCreatedAt = createdAt.toRelative();
|
||||||
const relativeUpdatedAt = updatedAt.toRelative();
|
const relativeUpdatedAt = updatedAt.toRelative();
|
||||||
|
@@ -66,15 +66,10 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog open onClose={cancel} sx={{ display: open ? 'block' : 'none' }}>
|
||||||
open
|
|
||||||
onClose={cancel}
|
|
||||||
sx={{ display: open ? 'block' : 'none' }}
|
|
||||||
data-test={`${subject}-role-conditions-modal`}
|
|
||||||
>
|
|
||||||
<DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle>
|
<DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent data-test="role-conditions-modal-body">
|
<DialogContent>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
@@ -118,7 +113,6 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
|
|||||||
{action.subjects.includes(subject) && (
|
{action.subjects.includes(subject) && (
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
|
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
|
||||||
dataTest={`${condition.key}-${action.key.toLowerCase()}-checkbox`}
|
|
||||||
defaultValue={defaultChecked}
|
defaultValue={defaultChecked}
|
||||||
disabled={
|
disabled={
|
||||||
getValues(
|
getValues(
|
||||||
|
@@ -62,7 +62,6 @@ const PermissionCatalogField = ({
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={subject.key}
|
key={subject.key}
|
||||||
sx={{ '&:last-child td': { border: 0 } }}
|
sx={{ '&:last-child td': { border: 0 } }}
|
||||||
data-test={`${subject.key}-permission-row`}
|
|
||||||
>
|
>
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography variant="subtitle2">{subject.label}</Typography>
|
<Typography variant="subtitle2">{subject.label}</Typography>
|
||||||
@@ -75,7 +74,6 @@ const PermissionCatalogField = ({
|
|||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
name={`${name}.${subject.key}.${action.key}.value`}
|
name={`${name}.${subject.key}.${action.key}.value`}
|
||||||
dataTest={`${action.key.toLowerCase()}-checkbox`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -91,7 +89,6 @@ const PermissionCatalogField = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => setDialogName(subject.key)}
|
onClick={() => setDialogName(subject.key)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
data-test="permission-settings-button"
|
|
||||||
>
|
>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -43,9 +43,6 @@ export default function ResetPasswordForm() {
|
|||||||
|
|
||||||
enqueueSnackbar(formatMessage('resetPasswordForm.passwordUpdated'), {
|
enqueueSnackbar(formatMessage('resetPasswordForm.passwordUpdated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
|
||||||
'data-test': 'snackbar-reset-password-success'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate(URLS.LOGIN);
|
navigate(URLS.LOGIN);
|
||||||
|
@@ -49,29 +49,21 @@ export default function RoleList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading && <ListLoader
|
{loading && <ListLoader rowsNumber={3} columnsNumber={2} />}
|
||||||
rowsNumber={3}
|
|
||||||
columnsNumber={2}
|
|
||||||
data-test="roles-list-loader" />}
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
roles.map((role) => (
|
roles.map((role) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={role.id}
|
key={role.id}
|
||||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
data-test="role-row"
|
|
||||||
>
|
>
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography
|
<Typography variant="subtitle2">{role.name}</Typography>
|
||||||
variant="subtitle2"
|
|
||||||
data-test="role-name"
|
|
||||||
>{role.name}</Typography>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography
|
<Typography variant="subtitle2">
|
||||||
variant="subtitle2"
|
{role.description}
|
||||||
data-test="role-description"
|
</Typography>
|
||||||
>{role.description}</Typography>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -80,7 +72,6 @@ export default function RoleList(): React.ReactElement {
|
|||||||
size="small"
|
size="small"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.ROLE(role.id)}
|
to={URLS.ROLE(role.id)}
|
||||||
data-test="role-edit"
|
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -88,7 +79,6 @@ export default function RoleList(): React.ReactElement {
|
|||||||
<DeleteRoleButton
|
<DeleteRoleButton
|
||||||
disabled={role.isAdmin}
|
disabled={role.isAdmin}
|
||||||
roleId={role.id}
|
roleId={role.id}
|
||||||
data-test="role-delete"
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@@ -52,7 +52,6 @@ export default function TablePaginationActions(
|
|||||||
onClick={handleFirstPageButtonClick}
|
onClick={handleFirstPageButtonClick}
|
||||||
disabled={page === 0}
|
disabled={page === 0}
|
||||||
aria-label="first page"
|
aria-label="first page"
|
||||||
data-test="first-page-button"
|
|
||||||
>
|
>
|
||||||
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -60,7 +59,6 @@ export default function TablePaginationActions(
|
|||||||
onClick={handleBackButtonClick}
|
onClick={handleBackButtonClick}
|
||||||
disabled={page === 0}
|
disabled={page === 0}
|
||||||
aria-label="previous page"
|
aria-label="previous page"
|
||||||
data-test="previous-page-button"
|
|
||||||
>
|
>
|
||||||
{theme.direction === 'rtl' ? (
|
{theme.direction === 'rtl' ? (
|
||||||
<KeyboardArrowRight />
|
<KeyboardArrowRight />
|
||||||
@@ -72,7 +70,6 @@ export default function TablePaginationActions(
|
|||||||
onClick={handleNextButtonClick}
|
onClick={handleNextButtonClick}
|
||||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||||
aria-label="next page"
|
aria-label="next page"
|
||||||
data-test="next-page-button"
|
|
||||||
>
|
>
|
||||||
{theme.direction === 'rtl' ? (
|
{theme.direction === 'rtl' ? (
|
||||||
<KeyboardArrowLeft />
|
<KeyboardArrowLeft />
|
||||||
@@ -84,7 +81,6 @@ export default function TablePaginationActions(
|
|||||||
onClick={handleLastPageButtonClick}
|
onClick={handleLastPageButtonClick}
|
||||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||||
aria-label="last page"
|
aria-label="last page"
|
||||||
data-test="last-page-button"
|
|
||||||
>
|
>
|
||||||
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -83,34 +83,23 @@ export default function UserList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading && <ListLoader
|
{loading && <ListLoader rowsNumber={3} columnsNumber={2} />}
|
||||||
data-test="users-list-loader"
|
|
||||||
rowsNumber={3}
|
|
||||||
columnsNumber={2} />}
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
users.map((user) => (
|
users.map((user) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={user.id}
|
key={user.id}
|
||||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
data-test="user-row"
|
|
||||||
>
|
>
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography
|
<Typography variant="subtitle2">{user.fullName}</Typography>
|
||||||
variant="subtitle2"
|
|
||||||
data-test="user-full-name">{user.fullName}</Typography>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="subtitle2">{user.email}</Typography>
|
||||||
variant="subtitle2"
|
|
||||||
data-test="user-email">{user.email}</Typography>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="subtitle2">
|
||||||
variant="subtitle2"
|
|
||||||
data-test="user-role"
|
|
||||||
>
|
|
||||||
{user.role.name}
|
{user.role.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -121,14 +110,11 @@ export default function UserList(): React.ReactElement {
|
|||||||
size="small"
|
size="small"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.USER(user.id)}
|
to={URLS.USER(user.id)}
|
||||||
data-test="user-edit"
|
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<DeleteUserButton
|
<DeleteUserButton userId={user.id} />
|
||||||
data-test="user-delete"
|
|
||||||
userId={user.id} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -138,8 +124,6 @@ export default function UserList(): React.ReactElement {
|
|||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
data-total-count={totalCount}
|
|
||||||
data-rows-per-page={rowsPerPage}
|
|
||||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
rowsPerPageOptions={[10, 25, 50, 100]}
|
||||||
page={page}
|
page={page}
|
||||||
count={totalCount}
|
count={totalCount}
|
||||||
|
@@ -20,11 +20,7 @@ export default function useEnqueueSnackbar() {
|
|||||||
...(options || {}) as Record<string, unknown>,
|
...(options || {}) as Record<string, unknown>,
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
onClick: () => closeSnackbar(key),
|
onClick: () => closeSnackbar(key),
|
||||||
...({
|
...(options.SnackbarProps || {}) as Record<string, unknown>
|
||||||
'data-test': 'snackbar', // keep above options.snackbarProps
|
|
||||||
'data-snackbar-variant': `${options.variant}` || 'default',
|
|
||||||
}) as Record<string, string>,
|
|
||||||
...(options.SnackbarProps || {}) as Record<string, unknown>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -12,7 +12,6 @@ import LiveChat from 'components/LiveChat/index.ee';
|
|||||||
import routes from 'routes';
|
import routes from 'routes';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Router>
|
<Router>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user