Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
d01477306d chore(deps): bump vite from 3.2.10 to 3.2.11
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 3.2.10 to 3.2.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v3.2.11/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v3.2.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 20:51:00 +00:00
78 changed files with 1046 additions and 1438 deletions

View File

@@ -23,7 +23,6 @@ env:
REDIS_HOST: localhost
APP_ENV: production
LICENSE_KEY: dummy_license_key
BACKEND_APP_URL: http://localhost:3000
jobs:
test:

View File

@@ -6,7 +6,7 @@ export default async (request, response) => {
const user = await User.query().insertAndFetch(await userParams(request));
await user.sendInvitationEmail();
renderObject(response, user, { status: 201, serializer: 'AdminUser' });
renderObject(response, user, { status: 201 });
};
const userParams = async (request) => {

View File

@@ -6,7 +6,7 @@ import User from '../../../../../models/user.js';
import Role from '../../../../../models/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import createUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/create-user.js';
import createUserMock from '../../../../../../test/mocks/rest/api/v1/users/create-user.js';
describe('POST /api/v1/admin/users', () => {
let currentUser, adminRole, token;

View File

@@ -1,22 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let step = await request.currentUser.authorizedSteps
.findById(request.params.stepId)
.throwIfNotFound();
step = await step.updateFor(request.currentUser, stepParams(request));
renderObject(response, step);
};
const stepParams = (request) => {
const { connectionId, appKey, key, parameters } = request.body;
return {
connectionId,
appKey,
key,
parameters,
};
};

View File

@@ -1,211 +0,0 @@
import { describe, it, beforeEach, expect } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import updateStepMock from '../../../../../test/mocks/rest/api/v1/steps/update-step.js';
describe('PATCH /api/v1/steps/:stepId', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update the step of the current user', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection({
key: 'deepl',
});
await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: currentUserConnection.id,
appKey: 'deepl',
key: 'translateText',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
const response = await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
parameters: {
text: 'Hello world!',
targetLanguage: 'de',
},
})
.expect(200);
const refetchedStep = await actionStep.$query();
const expectedResponse = updateStepMock(refetchedStep);
expect(response.body).toStrictEqual(expectedResponse);
});
it('should update the step of the another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection({
key: 'deepl',
});
await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
const actionStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
const response = await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
parameters: {
text: 'Hello world!',
targetLanguage: 'de',
},
})
.expect(200);
const refetchedStep = await actionStep.$query();
const expectedResponse = updateStepMock(refetchedStep);
expect(response.body).toStrictEqual(expectedResponse);
});
it('should return not found response for inaccessible connection', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
key: 'deepl',
userId: anotherUser.id,
});
await createStep({
flowId: currentUserFlow.id,
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUser.roleId,
conditions: ['isCreator'],
});
await request(app)
.patch(`/api/v1/steps/${actionStep.id}`)
.set('Authorization', token)
.send({
connectionId: anotherUserConnection.id,
})
.expect(404);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.patch(`/api/v1/steps/${notExistingStepUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUser.roleId,
conditions: [],
});
await request(app)
.patch('/api/v1/steps/invalidStepUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,19 +1,36 @@
import updateStep from './mutations/update-step.js';
// Converted mutations
import executeFlow from './mutations/execute-flow.js';
import updateUser from './mutations/update-user.ee.js';
import deleteStep from './mutations/delete-step.js';
import verifyConnection from './mutations/verify-connection.js';
import createFlow from './mutations/create-flow.js';
import deleteCurrentUser from './mutations/delete-current-user.ee.js';
import updateCurrentUser from './mutations/update-current-user.js';
import generateAuthUrl from './mutations/generate-auth-url.js';
import createConnection from './mutations/create-connection.js';
import deleteFlow from './mutations/delete-flow.js';
import resetConnection from './mutations/reset-connection.js';
import updateConnection from './mutations/update-connection.js';
import createUser from './mutations/create-user.ee.js';
import updateFlowStatus from './mutations/update-flow-status.js';
const mutationResolvers = {
createConnection,
createFlow,
createUser,
deleteCurrentUser,
deleteFlow,
deleteStep,
executeFlow,
generateAuthUrl,
resetConnection,
updateConnection,
updateCurrentUser,
updateFlowStatus,
updateStep,
updateUser,
verifyConnection,
};

View File

@@ -0,0 +1,45 @@
import App from '../../models/app.js';
import Step from '../../models/step.js';
const createFlow = async (_parent, params, context) => {
context.currentUser.can('create', 'Flow');
const connectionId = params?.input?.connectionId;
const appKey = params?.input?.triggerAppKey;
if (appKey) {
await App.findOneByKey(appKey);
}
const flow = await context.currentUser.$relatedQuery('flows').insert({
name: 'Name your flow',
});
if (connectionId) {
const hasConnection = await context.currentUser
.$relatedQuery('connections')
.findById(connectionId);
if (!hasConnection) {
throw new Error('The connection does not exist!');
}
}
await Step.query().insert({
flowId: flow.id,
type: 'trigger',
position: 1,
appKey,
connectionId,
});
await Step.query().insert({
flowId: flow.id,
type: 'action',
position: 2,
});
return flow;
};
export default createFlow;

View File

@@ -0,0 +1,66 @@
import appConfig from '../../config/app.js';
import User from '../../models/user.js';
import Role from '../../models/role.js';
import emailQueue from '../../queues/email.js';
import {
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../../helpers/remove-job-configuration.js';
const createUser = async (_parent, params, context) => {
context.currentUser.can('create', 'User');
const { fullName, email } = params.input;
const existingUser = await User.query().findOne({
email: email.toLowerCase(),
});
if (existingUser) {
throw new Error('User already exists!');
}
const userPayload = {
fullName,
email,
status: 'invited',
};
try {
context.currentUser.can('update', 'Role');
userPayload.roleId = params.input.role.id;
} catch {
// void
const role = await Role.findAdmin();
userPayload.roleId = role.id;
}
const user = await User.query().insert(userPayload);
await user.generateInvitationToken();
const jobName = `Invitation Email - ${user.id}`;
const acceptInvitationUrl = `${appConfig.webAppUrl}/accept-invitation?token=${user.invitationToken}`;
const jobPayload = {
email: user.email,
subject: 'You are invited!',
template: 'invitation-instructions',
params: {
fullName: user.fullName,
acceptInvitationUrl,
},
};
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
};
await emailQueue.add(jobName, jobPayload, jobOptions);
return { user, acceptInvitationUrl };
};
export default createUser;

View File

@@ -0,0 +1,58 @@
import { Duration } from 'luxon';
import deleteUserQueue from '../../queues/delete-user.ee.js';
import flowQueue from '../../queues/flow.js';
import Flow from '../../models/flow.js';
import ExecutionStep from '../../models/execution-step.js';
import appConfig from '../../config/app.js';
const deleteCurrentUser = async (_parent, params, context) => {
const id = context.currentUser.id;
const flows = await context.currentUser.$relatedQuery('flows').where({
active: true,
});
const repeatableJobs = await flowQueue.getRepeatableJobs();
for (const flow of flows) {
const job = repeatableJobs.find((job) => job.id === flow.id);
if (job) {
await flowQueue.removeRepeatableByKey(job.key);
}
}
const executionIds = (
await context.currentUser
.$relatedQuery('executions')
.select('executions.id')
).map((execution) => execution.id);
const flowIds = flows.map((flow) => flow.id);
await ExecutionStep.query().delete().whereIn('execution_id', executionIds);
await context.currentUser.$relatedQuery('executions').delete();
await context.currentUser.$relatedQuery('steps').delete();
await Flow.query().whereIn('id', flowIds).delete();
await context.currentUser.$relatedQuery('connections').delete();
await context.currentUser.$relatedQuery('identities').delete();
if (appConfig.isCloud) {
await context.currentUser.$relatedQuery('subscriptions').delete();
await context.currentUser.$relatedQuery('usageData').delete();
}
await context.currentUser.$query().delete();
const jobName = `Delete user - ${id}`;
const jobPayload = { id };
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
const jobOptions = {
delay: millisecondsFor30Days,
};
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
return true;
};
export default deleteCurrentUser;

View File

@@ -0,0 +1,53 @@
import Flow from '../../models/flow.js';
import ExecutionStep from '../../models/execution-step.js';
import globalVariable from '../../helpers/global-variable.js';
import logger from '../../helpers/logger.js';
const deleteFlow = async (_parent, params, context) => {
const conditions = context.currentUser.can('delete', 'Flow');
const isCreator = conditions.isCreator;
const allFlows = Flow.query();
const userFlows = context.currentUser.$relatedQuery('flows');
const baseQuery = isCreator ? userFlows : allFlows;
const flow = await baseQuery
.findOne({
id: params.input.id,
})
.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 = (
await flow.$relatedQuery('executions').select('executions.id')
).map((execution) => execution.id);
await ExecutionStep.query().delete().whereIn('execution_id', executionIds);
await flow.$relatedQuery('executions').delete();
await flow.$relatedQuery('steps').delete();
await flow.$query().delete();
return;
};
export default deleteFlow;

View File

@@ -0,0 +1,40 @@
import Step from '../../models/step.js';
const deleteStep = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const isCreator = conditions.isCreator;
const allSteps = Step.query();
const userSteps = context.currentUser.$relatedQuery('steps');
const baseQuery = isCreator ? userSteps : allSteps;
const step = await baseQuery
.withGraphFetched('flow')
.findOne({
'steps.id': params.input.id,
})
.throwIfNotFound();
await step.$relatedQuery('executionSteps').delete();
await step.$query().delete();
const nextSteps = await step.flow
.$relatedQuery('steps')
.where('position', '>', step.position);
const nextStepQueries = nextSteps.map(async (nextStep) => {
await nextStep.$query().patch({
position: nextStep.position - 1,
});
});
await Promise.all(nextStepQueries);
step.flow = await step.flow
.$query()
.withGraphJoined('steps')
.orderBy('steps.position', 'asc');
return step;
};
export default deleteStep;

View File

@@ -0,0 +1,91 @@
import Flow from '../../models/flow.js';
import flowQueue from '../../queues/flow.js';
import {
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../../helpers/remove-job-configuration.js';
import globalVariable from '../../helpers/global-variable.js';
const JOB_NAME = 'flow';
const EVERY_15_MINUTES_CRON = '*/15 * * * *';
const updateFlowStatus = async (_parent, params, context) => {
const conditions = context.currentUser.can('publish', 'Flow');
const isCreator = conditions.isCreator;
const allFlows = Flow.query();
const userFlows = context.currentUser.$relatedQuery('flows');
const baseQuery = isCreator ? userFlows : allFlows;
let flow = await baseQuery
.clone()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
const newActiveValue = params.input.active;
if (flow.active === newActiveValue) {
return flow;
}
const triggerStep = await flow.getTriggerStep();
if (triggerStep.status === 'incomplete') {
throw flow.IncompleteStepsError;
}
const trigger = await triggerStep.getTriggerCommand();
const interval = trigger.getInterval?.(triggerStep.parameters);
const repeatOptions = {
pattern: interval || EVERY_15_MINUTES_CRON,
};
if (trigger.type === 'webhook') {
const $ = await globalVariable({
flow,
connection: await triggerStep.$relatedQuery('connection'),
app: await triggerStep.getApp(),
step: triggerStep,
testRun: false,
});
if (newActiveValue && trigger.registerHook) {
await trigger.registerHook($);
} else if (!newActiveValue && trigger.unregisterHook) {
await trigger.unregisterHook($);
}
} else {
if (newActiveValue) {
flow = await flow.$query().patchAndFetch({
publishedAt: new Date().toISOString(),
});
const jobName = `${JOB_NAME}-${flow.id}`;
await flowQueue.add(
jobName,
{ flowId: flow.id },
{
repeat: repeatOptions,
jobId: flow.id,
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
);
} else {
const repeatableJobs = await flowQueue.getRepeatableJobs();
const job = repeatableJobs.find((job) => job.id === flow.id);
await flowQueue.removeRepeatableByKey(job.key);
}
}
flow = await flow.$query().withGraphFetched('steps').patchAndFetch({
active: newActiveValue,
});
return flow;
};
export default updateFlowStatus;

View File

@@ -0,0 +1,68 @@
import App from '../../models/app.js';
import Step from '../../models/step.js';
import Connection from '../../models/connection.js';
const updateStep = async (_parent, params, context) => {
const { isCreator } = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const baseQuery = isCreator ? userSteps : allSteps;
const { input } = params;
let step = await baseQuery
.findOne({
'steps.id': input.id,
flow_id: input.flow.id,
})
.throwIfNotFound();
if (input.connection.id) {
let canSeeAllConnections = false;
try {
const conditions = context.currentUser.can('read', 'Connection');
canSeeAllConnections = !conditions.isCreator;
} catch {
// void
}
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseConnectionsQuery = canSeeAllConnections
? allConnections
: userConnections;
const connection = await baseConnectionsQuery
.clone()
.findById(input.connection?.id);
if (!connection) {
throw new Error('The connection does not exist!');
}
}
if (step.isTrigger) {
await App.checkAppAndTrigger(input.appKey, input.key);
}
if (step.isAction) {
await App.checkAppAndAction(input.appKey, input.key);
}
step = await Step.query()
.patchAndFetchById(input.id, {
key: input.key,
appKey: input.appKey,
connectionId: input.connection.id,
parameters: input.parameters,
status: 'incomplete'
})
.withGraphFetched('connection');
await step.updateWebhookUrl();
return step;
};
export default updateStep;

View File

@@ -0,0 +1,27 @@
import User from '../../models/user.js';
const updateUser = async (_parent, params, context) => {
context.currentUser.can('update', 'User');
const userPayload = {
email: params.input.email,
fullName: params.input.fullName,
};
try {
context.currentUser.can('update', 'Role');
userPayload.roleId = params.input.role.id;
} catch {
// void
}
const user = await User.query().patchAndFetchById(
params.input.id,
userPayload
);
return user;
};
export default updateUser;

View File

@@ -3,11 +3,19 @@ type Query {
}
type Mutation {
createConnection(input: CreateConnectionInput): Connection
createFlow(input: CreateFlowInput): Flow
createUser(input: CreateUserInput): UserWithAcceptInvitationUrl
deleteCurrentUser: Boolean
deleteFlow(input: DeleteFlowInput): Boolean
deleteStep(input: DeleteStepInput): Step
executeFlow(input: ExecuteFlowInput): executeFlowType
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
resetConnection(input: ResetConnectionInput): Connection
updateConnection(input: UpdateConnectionInput): Connection
updateCurrentUser(input: UpdateCurrentUserInput): User
updateFlowStatus(input: UpdateFlowStatusInput): Flow
updateStep(input: UpdateStepInput): Step
updateUser(input: UpdateUserInput): User
verifyConnection(input: VerifyConnectionInput): Connection
}
@@ -232,14 +240,56 @@ input VerifyConnectionInput {
id: String!
}
input CreateFlowInput {
triggerAppKey: String
connectionId: String
}
input UpdateFlowStatusInput {
id: String!
active: Boolean!
}
input ExecuteFlowInput {
stepId: String!
}
input DeleteFlowInput {
id: String!
}
input UpdateStepInput {
id: String
previousStepId: String
key: String
appKey: String
connection: StepConnectionInput
flow: StepFlowInput
parameters: JSONObject
previousStep: PreviousStepInput
}
input DeleteStepInput {
id: String!
}
input CreateUserInput {
fullName: String!
email: String!
role: UserRoleInput!
}
input UserRoleInput {
id: String
}
input UpdateUserInput {
id: String!
fullName: String
email: String
role: UserRoleInput
}
input UpdateCurrentUserInput {
email: String
password: String
@@ -323,6 +373,11 @@ type User {
updatedAt: String
}
type UserWithAcceptInvitationUrl {
user: User
acceptInvitationUrl: String
}
type Role {
id: String
name: String

View File

@@ -37,10 +37,6 @@ const authorizationList = {
action: 'read',
subject: 'Flow',
},
'PATCH /api/v1/steps/:stepId': {
action: 'update',
subject: 'Flow',
},
'POST /api/v1/steps/:stepId/test': {
action: 'update',
subject: 'Flow',

View File

@@ -125,13 +125,7 @@ class Flow extends Base {
get IncompleteStepsError() {
return new ValidationError({
data: {
flow: [
{
message: 'All steps should be completed before updating flow status!'
}
],
},
message: 'All steps should be completed before updating flow status!',
type: 'incompleteStepsError',
});
}
@@ -372,13 +366,8 @@ class Flow extends Base {
if (allSteps.length < 2) {
throw new ValidationError({
data: {
flow: [
{
message: 'There should be at least one trigger and one action steps in the flow!'
}
],
},
message:
'There should be at least one trigger and one action steps in the flow!',
type: 'insufficientStepsError',
});
}

View File

@@ -334,39 +334,6 @@ class Step extends Base {
await Promise.all(nextStepQueries);
}
async updateFor(user, newStepData) {
const { connectionId, appKey, key, parameters } = newStepData;
if (connectionId && (appKey || this.appKey)) {
await user.authorizedConnections
.findOne({
id: connectionId,
key: appKey || this.appKey,
})
.throwIfNotFound();
}
if (this.isTrigger && appKey && key) {
await App.checkAppAndTrigger(appKey, key);
}
if (this.isAction && appKey && key) {
await App.checkAppAndAction(appKey, key);
}
const updatedStep = await this.$query().patchAndFetch({
key: key,
appKey: appKey,
connectionId: connectionId,
parameters: parameters,
status: 'incomplete',
});
await updatedStep.updateWebhookUrl();
return updatedStep;
}
}
export default Step;

View File

@@ -180,10 +180,6 @@ class User extends Base {
},
});
static get virtualAttributes() {
return ['acceptInvitationUrl'];
}
get authorizedFlows() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
@@ -208,10 +204,6 @@ class User extends Base {
: Execution.query();
}
get acceptInvitationUrl() {
return `${appConfig.webAppUrl}/accept-invitation?token=${this.invitationToken}`;
}
static async authenticate(email, password) {
const user = await User.query().findOne({
email: email?.toLowerCase() || null,
@@ -370,6 +362,7 @@ class User extends Base {
await this.generateInvitationToken();
const jobName = `Invitation Email - ${this.id}`;
const acceptInvitationUrl = `${appConfig.webAppUrl}/accept-invitation?token=${this.invitationToken}`;
const jobPayload = {
email: this.email,
@@ -377,7 +370,7 @@ class User extends Base {
template: 'invitation-instructions',
params: {
fullName: this.fullName,
acceptInvitationUrl: this.acceptInvitationUrl,
acceptInvitationUrl,
},
};

View File

@@ -7,7 +7,6 @@ import getPreviousStepsAction from '../../../controllers/api/v1/steps/get-previo
import createDynamicFieldsAction from '../../../controllers/api/v1/steps/create-dynamic-fields.js';
import createDynamicDataAction from '../../../controllers/api/v1/steps/create-dynamic-data.js';
import deleteStepAction from '../../../controllers/api/v1/steps/delete-step.js';
import updateStepAction from '../../../controllers/api/v1/steps/update-step.js';
const router = Router();
@@ -41,7 +40,6 @@ router.post(
createDynamicDataAction
);
router.patch('/:stepId', authenticateUser, authorizeUser, updateStepAction);
router.delete('/:stepId', authenticateUser, authorizeUser, deleteStepAction);
export default router;

View File

@@ -1,11 +0,0 @@
import userSerializer from '../user.js';
const adminUserSerializer = (user) => {
const userData = userSerializer(user);
userData.acceptInvitationUrl = user.acceptInvitationUrl;
return userData;
};
export default adminUserSerializer;

View File

@@ -1,19 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createUser } from '../../../test/factories/user';
import adminUserSerializer from './user.js';
describe('adminUserSerializer', () => {
let user;
beforeEach(async () => {
user = await createUser();
});
it('should return user data with accept invitation url', async () => {
const serializedUser = adminUserSerializer(user);
expect(serializedUser.acceptInvitationUrl).toEqual(
user.acceptInvitationUrl
);
});
});

View File

@@ -16,10 +16,8 @@ import actionSerializer from './action.js';
import executionSerializer from './execution.js';
import executionStepSerializer from './execution-step.js';
import subscriptionSerializer from './subscription.ee.js';
import adminUserSerializer from './admin/user.js';
const serializers = {
AdminUser: adminUserSerializer,
User: userSerializer,
Role: roleSerializer,
Permission: permissionSerializer,

View File

@@ -1,31 +0,0 @@
import { ValidationError } from 'objection';
export const toRequireProperty = async (model, requiredProperty) => {
try {
await model.query().insert({});
} catch (error) {
if (
error instanceof ValidationError &&
error.message.includes(
`${requiredProperty}: must have required property '${requiredProperty}'`
)
) {
return {
pass: true,
message: () =>
`Expected ${requiredProperty} to be required, and it was.`,
};
} else {
return {
pass: false,
message: () =>
`Expected ${requiredProperty} to be required, but it was not found in the error message.`,
};
}
}
return {
pass: false,
message: () =>
`Expected ${requiredProperty} to be required, but no ValidationError was thrown.`,
};
};

View File

@@ -1,30 +0,0 @@
import appConfig from '../../../../../../../src/config/app.js';
const createUserMock = (user) => {
const userData = {
createdAt: user.createdAt.getTime(),
email: user.email,
fullName: user.fullName,
id: user.id,
status: user.status,
updatedAt: user.updatedAt.getTime(),
acceptInvitationUrl: user.acceptInvitationUrl,
};
if (appConfig.isCloud && user.trialExpiryDate) {
userData.trialExpiryDate = user.trialExpiryDate.toISOString();
}
return {
data: userData,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'User',
},
};
};
export default createUserMock;

View File

@@ -1,26 +0,0 @@
const updateStepMock = (step) => {
const data = {
id: step.id,
type: step.type || 'action',
key: step.key || null,
appKey: step.appKey || null,
iconUrl: step.iconUrl || null,
webhookUrl: step.webhookUrl || null,
status: step.status || 'incomplete',
position: step.position,
parameters: step.parameters || {},
};
return {
data,
meta: {
type: 'Step',
count: 1,
isArray: false,
currentPage: null,
totalPages: null,
},
};
};
export default updateStepMock;

View File

@@ -2,7 +2,6 @@ import { Model } from 'objection';
import { client as knex } from '../../src/config/database.js';
import logger from '../../src/helpers/logger.js';
import { vi } from 'vitest';
import './insert-assertions.js';
global.beforeAll(async () => {
global.knex = null;

View File

@@ -1,8 +0,0 @@
import { expect } from 'vitest';
import { toRequireProperty } from '../assertions/to-require-property';
expect.extend({
async toRequireProperty(model, requiredProperty) {
return await toRequireProperty(model, requiredProperty);
},
});

View File

@@ -1,40 +0,0 @@
import { expect } from '@playwright/test';
const { AuthenticatedPage } = require('../authenticated-page');
export class AdminApplicationAuthClientsPage extends AuthenticatedPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.authClientsTab = this.page.getByText('AUTH CLIENTS');
this.saveButton = this.page.getByTestId('submitButton');
this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success');
this.createFirstAuthClientButton = this.page.getByTestId('no-results');
this.createAuthClientButton = this.page.getByTestId('create-auth-client-button');
this.submitAuthClientFormButton = this.page.getByTestId('submit-auth-client-form');
this.authClientEntry = this.page.getByTestId('auth-client');
}
async openAuthClientsTab() {
this.authClientsTab.click();
}
async openFirstAuthClientCreateForm() {
this.createFirstAuthClientButton.click();
}
async openAuthClientCreateForm() {
this.createAuthClientButton.click();
}
async submitAuthClientForm() {
this.submitAuthClientFormButton.click();
}
async authClientShouldBeVisible(authClientName) {
await expect(this.authClientEntry.filter({ hasText: authClientName })).toBeVisible();
}
}

View File

@@ -1,59 +0,0 @@
const { AuthenticatedPage } = require('../authenticated-page');
const { expect } = require('@playwright/test');
export class AdminApplicationSettingsPage extends AuthenticatedPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.allowCustomConnectionsSwitch = this.page.locator('[name="allowCustomConnection"]');
this.allowSharedConnectionsSwitch = this.page.locator('[name="shared"]');
this.disableConnectionsSwitch = this.page.locator('[name="disabled"]');
this.saveButton = this.page.getByTestId('submit-button');
this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success');
}
async allowCustomConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.allowCustomConnectionsSwitch.check();
await this.saveButton.click();
}
async allowSharedConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.allowSharedConnectionsSwitch.check();
await this.saveButton.click();
}
async disallowConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.disableConnectionsSwitch.check();
await this.saveButton.click();
}
async disallowCustomConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.allowCustomConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async disallowSharedConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.allowSharedConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async allowConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.disableConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async expectSuccessSnackbarToBeVisible() {
await expect(this.successSnackbar).toHaveCount(1);
await this.successSnackbar.click();
await expect(this.successSnackbar).toHaveCount(0);
}
}

View File

@@ -1,32 +0,0 @@
const { AuthenticatedPage } = require('../authenticated-page');
export class AdminApplicationsPage extends AuthenticatedPage {
screenshotPath = '/admin-settings/apps';
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.searchInput = page.locator('[id="search-input"]');
this.appRow = page.getByTestId('app-row');
this.appsDrawerLink = page.getByTestId('apps-drawer-link');
this.appsLoader = page.getByTestId('apps-loader');
}
async openApplication(appName) {
await this.searchInput.fill(appName);
await this.appRow.locator(this.page.getByText(appName)).click();
}
async navigateTo() {
await this.profileMenuButton.click();
await this.adminMenuItem.click();
await this.appsDrawerLink.click();
await this.isMounted();
await this.appsLoader.waitFor({
state: 'detached',
});
}
}

View File

@@ -6,10 +6,6 @@ const { AdminRolesPage } = require('./roles-page');
const { AdminCreateRolePage } = require('./create-role-page');
const { AdminEditRolePage } = require('./edit-role-page');
const { AdminApplicationsPage } = require('./applications-page');
const { AdminApplicationSettingsPage } = require('./application-settings-page');
const { AdminApplicationAuthClientsPage } = require('./application-auth-clients-page');
export const adminFixtures = {
adminUsersPage: async ({ page }, use) => {
await use(new AdminUsersPage(page));
@@ -17,26 +13,17 @@ export const adminFixtures = {
adminCreateUserPage: async ({ page }, use) => {
await use(new AdminCreateUserPage(page));
},
adminEditUserPage: async ({ page }, use) => {
adminEditUserPage: async ({page}, use) => {
await use(new AdminEditUserPage(page));
},
adminRolesPage: async ({ page }, use) => {
adminRolesPage: async ({ page}, use) => {
await use(new AdminRolesPage(page));
},
adminEditRolePage: async ({ page }, use) => {
adminEditRolePage: async ({ page}, use) => {
await use(new AdminEditRolePage(page));
},
adminCreateRolePage: async ({ page }, use) => {
adminCreateRolePage: async ({ page}, use) => {
await use(new AdminCreateRolePage(page));
},
adminApplicationsPage: async ({ page }, use) => {
await use(new AdminApplicationsPage(page));
},
adminApplicationSettingsPage: async ({ page }, use) => {
await use(new AdminApplicationSettingsPage(page));
},
adminApplicationAuthClientsPage: async ({ page }, use) => {
await use(new AdminApplicationAuthClientsPage(page));
}
};

View File

@@ -1,11 +0,0 @@
const { expect } = require('../fixtures/index');
export const expectNoDelayedJobForFlow = async (flowId, request) => {
const token = btoa(`${process.env.BULLMQ_DASHBOARD_USERNAME}:${process.env.BULLMQ_DASHBOARD_PASSWORD}`);
const queues = await request.get(`${process.env.BACKEND_APP_URL}/admin/queues/api/queues?activeQueue=flow&status=delayed&page=1`, {
headers: {'Authorization': `Basic ${token}`}
});
const queuesJsonResponse = await queues.json();
const flowQueue = queuesJsonResponse.queues.find(queue => queue.name === "flow");
await expect(flowQueue.jobs.find(job => job.name === `flow-${flowId}`)).toBeUndefined();
};

View File

@@ -24,21 +24,22 @@ export class FlowEditorPage extends AuthenticatedPage {
this.unpublishFlowButton = this.page.getByTestId('unpublish-flow-button');
this.publishFlowButton = this.page.getByTestId('publish-flow-button');
this.infoSnackbar = this.page.getByTestId('flow-cannot-edit-info-snackbar');
this.errorSnackbar = this.page.getByTestId('snackbar-error');
this.trigger = this.page.getByLabel('Trigger on weekends?');
this.stepCircularLoader = this.page.getByTestId('step-circular-loader');
this.flowName = this.page.getByTestId('editableTypography');
this.flowNameInput = this.page
.getByTestId('editableTypographyInput')
.locator('input');
this.flowStep = this.page.getByTestId('flow-step');
this.rssFeedUrl = this.page.getByTestId('parameters.feedUrl-text');
}
async createWebhookTrigger(workSynchronously) {
this.chooseAppAndTrigger('Webhook', 'Catch raw webhook');
await this.appAutocomplete.click();
await this.page.getByRole('option', { name: 'Webhook' }).click();
await expect(this.eventAutocomplete).toBeVisible();
await this.eventAutocomplete.click();
await this.page.getByRole('option', { name: 'Catch raw webhook' }).click();
await this.continueButton.click();
await this.page
.getByTestId('parameters.workSynchronously-autocomplete')
.click();
@@ -66,25 +67,12 @@ export class FlowEditorPage extends AuthenticatedPage {
return await webhookUrl.inputValue();
}
async chooseAppAndTrigger(appName, triggerEvent) {
await expect(this.appAutocomplete).toHaveCount(1);
await this.appAutocomplete.click();
await this.page.getByRole('option', { name: appName }).click();
await expect(this.eventAutocomplete).toBeVisible();
await this.eventAutocomplete.click();
await Promise.all([
this.page.waitForResponse(resp => /(apps\/.*\/triggers\/.*\/substeps)/.test(resp.url()) && resp.status() === 200),
this.page.getByRole('option', { name: triggerEvent }).click(),
]);
await this.continueButton.click();
}
async chooseAppAndEvent(appName, eventName) {
await expect(this.appAutocomplete).toHaveCount(1);
await this.appAutocomplete.click();
await this.page.getByRole('option', { name: appName }).click();
await expect(this.eventAutocomplete).toBeVisible();
await this.eventAutocomplete.click();
await expect(this.page.locator('[data-testid="ErrorIcon"]')).toHaveCount(2);
await Promise.all([
this.page.waitForResponse(resp => /(apps\/.*\/actions\/.*\/substeps)/.test(resp.url()) && resp.status() === 200),
this.page.getByRole('option', { name: eventName }).click(),
@@ -98,10 +86,4 @@ export class FlowEditorPage extends AuthenticatedPage {
await expect(this.testOutput).toBeVisible();
await this.continueButton.click();
}
async dismissErrorSnackbar() {
await expect(this.errorSnackbar).toBeVisible();
await this.errorSnackbar.click();
await expect(this.errorSnackbar).toHaveCount(0);
}
}

View File

@@ -1,9 +1,6 @@
const { Pool } = require('pg');
const { Client } = require('pg');
const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
const client = new Client({
host: process.env.POSTGRES_HOST,
user: process.env.POSTGRES_USERNAME,
port: process.env.POSTGRES_PORT,
@@ -11,4 +8,4 @@ const pool = new Pool({
database: process.env.POSTGRES_DATABASE
});
exports.pgPool = pool;
exports.client = client;

View File

@@ -1,18 +0,0 @@
const { pgPool } = require('./postgres-config');
const { expect } = require('../../fixtures/index');
export const flowShouldNotHavePublishedAtDateFilled = async (flowId) => {
const queryFlow = {
text: 'SELECT * FROM flows WHERE id = $1',
values: [flowId]
};
try {
const queryFlowResult = await pgPool.query(queryFlow);
expect(queryFlowResult.rowCount).toEqual(1);
expect(queryFlowResult.rows[0].published_at).toBeNull();
} catch (err) {
console.error(err.message);
throw err;
}
};

View File

@@ -1,231 +0,0 @@
const { test, expect } = require('../../fixtures/index');
const { pgPool } = require('../../fixtures/postgres/postgres-config');
test.describe('Admin Applications', () => {
test.beforeAll(async () => {
const deleteAppAuthClients = {
text: 'DELETE FROM app_auth_clients WHERE app_key in ($1, $2, $3, $4, $5)',
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
};
const deleteAppConfigs = {
text: 'DELETE FROM app_configs WHERE key in ($1, $2, $3, $4, $5)',
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
};
try {
const deleteAppAuthClientsResult = await pgPool.query(deleteAppAuthClients);
expect(deleteAppAuthClientsResult.command).toBe('DELETE');
const deleteAppConfigsResult = await pgPool.query(deleteAppConfigs);
expect(deleteAppConfigsResult.command).toBe('DELETE');
} catch (err) {
console.error(err.message);
throw err;
}
});
test.beforeEach(async ({ adminApplicationsPage }) => {
await adminApplicationsPage.navigateTo();
});
test('Admin should be able to toggle Application settings', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
page
}) => {
await adminApplicationsPage.openApplication('Carbone');
await expect(page.url()).toContain('/admin-settings/apps/carbone/settings');
await adminApplicationSettingsPage.allowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.allowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.reload();
await adminApplicationSettingsPage.disallowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.disallowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.allowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
});
test('should allow only custom connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('Spotify');
await expect(page.url()).toContain('/admin-settings/apps/spotify/settings');
await adminApplicationSettingsPage.allowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Spotify", "Create Playlist");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
await expect(newConnectionOption).toBeEnabled();
await expect(newConnectionOption).toHaveCount(1);
await expect(newSharedConnectionOption).toHaveCount(0);
});
test('should allow only shared connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
adminApplicationAuthClientsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('Reddit');
await expect(page.url()).toContain('/admin-settings/apps/reddit/settings');
await adminApplicationSettingsPage.allowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationAuthClientsPage.openAuthClientsTab();
await adminApplicationAuthClientsPage.openFirstAuthClientCreateForm();
const authClientForm = page.getByTestId("auth-client-form");
await authClientForm.locator(page.getByTestId('switch')).check();
await authClientForm.locator(page.locator('[name="name"]')).fill('redditAuthClient');
await authClientForm.locator(page.locator('[name="clientId"]')).fill('redditClientId');
await authClientForm.locator(page.locator('[name="clientSecret"]')).fill('redditClientSecret');
await adminApplicationAuthClientsPage.submitAuthClientForm();
await adminApplicationAuthClientsPage.authClientShouldBeVisible('redditAuthClient');
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Reddit", "Create link post");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toBeEnabled();
await expect(newSharedConnectionOption).toHaveCount(1);
});
test('should not allow any connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('DeepL');
await expect(page.url()).toContain('/admin-settings/apps/deepl/settings');
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("DeepL", "Translate text");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
await expect(noConnectionsOption).toHaveCount(1);
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toHaveCount(0);
});
test('should not allow new connections but only already created', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
const queryUser = {
text: 'SELECT * FROM users WHERE email = $1',
values: [process.env.LOGIN_EMAIL]
};
try {
const queryUserResult = await pgPool.query(queryUser);
expect(queryUserResult.rowCount).toEqual(1);
const createMailchimpConnection = {
text: 'INSERT INTO connections (key, data, user_id, verified, draft) VALUES ($1, $2, $3, $4, $5)',
values: [
'mailchimp',
"U2FsdGVkX1+cAtdHwLiuRL4DaK/T1aljeeKyPMmtWK0AmAIsKhYwQiuyQCYJO3mdZ31z73hqF2Y+yj2Kn2/IIpLRqCxB2sC0rCDCZyolzOZ290YcBXSzYRzRUxhoOcZEtwYDKsy8AHygKK/tkj9uv9k6wOe1LjipNik4VmRhKjEYizzjLrJpbeU1oY+qW0GBpPYomFTeNf+MejSSmsUYyYJ8+E/4GeEfaonvsTSwMT7AId98Lck6Vy4wrfgpm7sZZ8xU15/HqXZNc8UCo2iTdw45xj/Oov9+brX4WUASFPG8aYrK8dl/EdaOvr89P8uIofbSNZ25GjJvVF5ymarrPkTZ7djjJXchzpwBY+7GTJfs3funR/vIk0Hq95jgOFFP1liZyqTXSa49ojG3hzojRQ==",
queryUserResult.rows[0].id,
'true',
'false'
],
};
const createMailchimpConnectionResult = await pgPool.query(createMailchimpConnection);
expect(createMailchimpConnectionResult.rowCount).toBe(1);
expect(createMailchimpConnectionResult.command).toBe('INSERT');
} catch (err) {
console.error(err.message);
throw err;
}
await adminApplicationsPage.openApplication('Mailchimp');
await expect(page.url()).toContain('/admin-settings/apps/mailchimp/settings');
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Mailchimp", "Create campaign");
await flowEditorPage.connectionAutocomplete.click();
await expect(page.getByRole('option').first()).toHaveText('Unnamed');
const existingConnection = page.getByRole('option').filter({ hasText: 'Unnamed' });
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
await expect(await existingConnection.count()).toBeGreaterThan(0);
await expect(noConnectionsOption).toHaveCount(0);
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toHaveCount(0);
});
});

View File

@@ -156,8 +156,15 @@ test.describe('User management page', () => {
'option', { name: 'Admin' }
).click();
await adminCreateUserPage.createButton.click();
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
await expect(snackbar.variant).toBe('error');
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();
}
);

View File

@@ -1,53 +0,0 @@
const { test, expect } = require('../../fixtures/index');
const { expectNoDelayedJobForFlow } = require('../../fixtures/bullmq-helper');
const { flowShouldNotHavePublishedAtDateFilled } = require('../../fixtures/postgres/postgres-helper');
test.describe('Flow Validation', () => {
test.beforeEach(async ({ page }) => {
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(page.getByTestId('flow-step')).toHaveCount(2);
});
test('Should not be able to publish flow without trigger', async ({
flowEditorPage,
page,
request
}) => {
const flowId = await page.url().split('editor/').pop();
await flowEditorPage.flowName.click();
await flowEditorPage.flowNameInput.fill('incompleteFlow');
await flowEditorPage.chooseAppAndTrigger('RSS', 'New items in feed');
await flowEditorPage.publishFlowButton.click();
await flowEditorPage.dismissErrorSnackbar();
await flowShouldNotHavePublishedAtDateFilled(flowId);
await expectNoDelayedJobForFlow(flowId, request);
await flowEditorPage.rssFeedUrl.fill('http://rss.cnn.com/rss/money_mostpopular.rss');
await expect(flowEditorPage.continueButton).toHaveCount(1);
await flowEditorPage.continueButton.click();
await flowEditorPage.publishFlowButton.click();
await flowEditorPage.dismissErrorSnackbar();
await flowShouldNotHavePublishedAtDateFilled(flowId);
await expectNoDelayedJobForFlow(flowId, request);
await expect(flowEditorPage.testOutput).not.toBeVisible();
await flowEditorPage.testAndContinueButton.click();
await expect(flowEditorPage.testOutput).toBeVisible();
await expect(flowEditorPage.hasNoOutput).not.toBeVisible();
await flowEditorPage.continueButton.click();
await flowEditorPage.publishFlowButton.click();
await expect(page.getByTestId('snackbar-error')).toBeVisible();
await flowShouldNotHavePublishedAtDateFilled(flowId);
await expectNoDelayedJobForFlow(flowId, request);
});
});

View File

@@ -3,10 +3,10 @@ import knex from 'knex';
import knexConfig from '../knexfile.js';
publicTest.describe('restore db', () => {
publicTest('clean db and perform migrations', async () => {
const knexClient = knex(knexConfig);
const migrator = knexClient.migrate;
await migrator.rollback({}, true);
await migrator.latest();
});
publicTest('clean db and perform migrations', async () => {
const knexClient = knex(knexConfig)
const migrator = knexClient.migrate;
await migrator.rollback({}, true);
await migrator.latest();
})
});

View File

@@ -1,5 +1,5 @@
const { publicTest, expect } = require('../../fixtures/index');
const { pgPool } = require('../../fixtures/postgres/postgres-config');
const { client } = require('../../fixtures/postgres-client-config');
const { DateTime } = require('luxon');
publicTest.describe('Accept invitation page', () => {
@@ -17,9 +17,17 @@ publicTest.describe('Accept invitation page', () => {
});
publicTest.describe('Accept invitation page - users', () => {
const expiredTokenDate = DateTime.now().minus({ days: 3 }).toISO();
const expiredTokenDate = DateTime.now().minus({days: 3}).toISO();
const token = (Math.random() + 1).toString(36).substring(2);
publicTest.beforeAll(async () => {
await client.connect();
});
publicTest.afterAll(async () => {
await client.end();
});
publicTest('should not be able to set the password if token is expired', async ({ acceptInvitationPage, adminCreateUserPage }) => {
adminCreateUserPage.seed(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
const user = adminCreateUserPage.generateUser();
@@ -30,7 +38,7 @@ publicTest.describe('Accept invitation page', () => {
};
try {
const queryRoleIdResult = await pgPool.query(queryRole);
const queryRoleIdResult = await client.query(queryRole);
expect(queryRoleIdResult.rowCount).toEqual(1);
const insertUser = {
@@ -38,7 +46,7 @@ publicTest.describe('Accept invitation page', () => {
values: [user.email, user.fullName, queryRoleIdResult.rows[0].id, 'invited', token, expiredTokenDate],
};
const insertUserResult = await pgPool.query(insertUser);
const insertUserResult = await client.query(insertUser);
expect(insertUserResult.rowCount).toBe(1);
expect(insertUserResult.command).toBe('INSERT');
} catch (err) {
@@ -60,7 +68,7 @@ publicTest.describe('Accept invitation page', () => {
};
try {
const queryRoleIdResult = await pgPool.query(queryRole);
const queryRoleIdResult = await client.query(queryRole);
expect(queryRoleIdResult.rowCount).toEqual(1);
const insertUser = {
@@ -68,7 +76,7 @@ publicTest.describe('Accept invitation page', () => {
values: [user.email, user.fullName, dateNow, queryRoleIdResult.rows[0].id, 'invited', token, dateNow],
};
const insertUserResult = await pgPool.query(insertUser);
const insertUserResult = await client.query(insertUser);
expect(insertUserResult.rowCount).toBe(1);
expect(insertUserResult.command).toBe('INSERT');
} catch (err) {

View File

@@ -27,12 +27,11 @@ import useLazyApps from 'hooks/useLazyApps';
function createConnectionOrFlow(appKey, supportsConnections = false) {
if (!supportsConnections) {
return URLS.CREATE_FLOW;
return URLS.CREATE_FLOW_WITH_APP(appKey);
}
return URLS.APP_ADD_CONNECTION(appKey);
}
function AddNewAppConnection(props) {
const { onClose } = props;
const theme = useTheme();

View File

@@ -49,7 +49,6 @@ function AdminApplicationAuthClientDialog(props) {
) : (
<DialogContentText tabIndex={-1} component="div">
<Form
data-test="auth-client-form"
onSubmit={submitHandler}
defaultValues={defaultValues}
render={({ formState: { isDirty } }) => (
@@ -68,7 +67,6 @@ function AdminApplicationAuthClientDialog(props) {
<InputCreator key={field.key} schema={field} />
))}
<LoadingButton
data-test="submit-auth-client-form"
type="submit"
variant="contained"
color="primary"

View File

@@ -43,7 +43,7 @@ function AdminApplicationAuthClients(props) {
return (
<div>
{sortedAuthClients.map((client) => (
<Card sx={{ mb: 1 }} key={client.id} data-test="auth-client">
<Card sx={{ mb: 1 }} key={client.id}>
<CardActionArea
component={Link}
to={URLS.ADMIN_APP_AUTH_CLIENT(appKey, client.id)}
@@ -70,7 +70,7 @@ function AdminApplicationAuthClients(props) {
))}
<Stack justifyContent="flex-end" direction="row">
<Link to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}>
<Button variant="contained" sx={{ mt: 2 }} component="div" data-test="create-auth-client-button">
<Button variant="contained" sx={{ mt: 2 }} component="div">
{formatMessage('createAuthClient.button')}
</Button>
</Link>

View File

@@ -87,7 +87,6 @@ function AdminApplicationSettings(props) {
</Stack>
<Stack>
<LoadingButton
data-test="submit-button"
type="submit"
variant="contained"
color="primary"

View File

@@ -43,7 +43,10 @@ function AppFlows(props) {
text={formatMessage('app.noFlows')}
data-test="flows-no-results"
{...(allowed && {
to: URLS.CREATE_FLOW,
to: URLS.CREATE_FLOW_WITH_APP_AND_CONNECTION(
appKey,
connectionId
),
})}
/>
)}

View File

@@ -1,36 +1,31 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import * as URLS from 'config/urls';
import ConfirmationDialog from 'components/ConfirmationDialog';
import apolloClient from 'graphql/client';
import { DELETE_CURRENT_USER } from 'graphql/mutations/delete-current-user.ee';
import useAuthentication from 'hooks/useAuthentication';
import useFormatMessage from 'hooks/useFormatMessage';
import useCurrentUser from 'hooks/useCurrentUser';
import useDeleteCurrentUser from 'hooks/useDeleteCurrentUser';
function DeleteAccountDialog(props) {
const [deleteCurrentUser] = useMutation(DELETE_CURRENT_USER);
const formatMessage = useFormatMessage();
const { data } = useCurrentUser();
const currentUser = data?.data;
const { mutateAsync: deleteCurrentUser } = useDeleteCurrentUser(
currentUser.id,
);
const authentication = useAuthentication();
const navigate = useNavigate();
const handleConfirm = React.useCallback(async () => {
await deleteCurrentUser();
authentication.removeToken();
await apolloClient.clearStore();
navigate(URLS.LOGIN);
}, [deleteCurrentUser, authentication, navigate]);
}, [deleteCurrentUser, currentUser]);
return (
<ConfirmationDialog

View File

@@ -1,17 +1,18 @@
import * as React from 'react';
import { useMutation } from '@apollo/client';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add';
import useCreateStep from 'hooks/useCreateStep';
import useUpdateStep from 'hooks/useUpdateStep';
import { UPDATE_STEP } from 'graphql/mutations/update-step';
import FlowStep from 'components/FlowStep';
import { FlowPropType } from 'propTypes/propTypes';
import { useQueryClient } from '@tanstack/react-query';
function Editor(props) {
const [updateStep] = useMutation(UPDATE_STEP);
const { flow } = props;
const { mutateAsync: updateStep } = useUpdateStep();
const { mutateAsync: createStep, isPending: isCreateStepPending } =
useCreateStep(flow?.id);
const [triggerStep] = flow.steps;
@@ -20,24 +21,29 @@ function Editor(props) {
const onStepChange = React.useCallback(
async (step) => {
const payload = {
const mutationInput = {
id: step.id,
key: step.key,
parameters: step.parameters,
connectionId: step.connection?.id,
connection: {
id: step.connection?.id,
},
flow: {
id: flow.id,
},
};
if (step.appKey) {
payload.appKey = step.appKey;
mutationInput.appKey = step.appKey;
}
await updateStep(payload);
await updateStep({ variables: { input: mutationInput } });
await queryClient.invalidateQueries({
queryKey: ['steps', step.id, 'connection'],
});
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
},
[updateStep, queryClient],
[updateStep, flow.id, queryClient],
);
const addStep = React.useCallback(

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { Link, useParams } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -15,11 +16,12 @@ import Container from 'components/Container';
import Editor from 'components/Editor';
import Can from 'components/Can';
import useFormatMessage from 'hooks/useFormatMessage';
import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
import * as URLS from 'config/urls';
import { TopBar } from './style';
import useFlow from 'hooks/useFlow';
import useUpdateFlow from 'hooks/useUpdateFlow';
import useUpdateFlowStatus from 'hooks/useUpdateFlowStatus';
import { useQueryClient } from '@tanstack/react-query';
import EditorNew from 'components/EditorNew/EditorNew';
const useNewFlowEditor = process.env.REACT_APP_USE_NEW_FLOW_EDITOR === 'true';
@@ -28,9 +30,10 @@ export default function EditorLayout() {
const { flowId } = useParams();
const formatMessage = useFormatMessage();
const { mutateAsync: updateFlow } = useUpdateFlow(flowId);
const { mutateAsync: updateFlowStatus } = useUpdateFlowStatus(flowId);
const [updateFlowStatus] = useMutation(UPDATE_FLOW_STATUS);
const { data, isLoading: isFlowLoading } = useFlow(flowId);
const flow = data?.data;
const queryClient = useQueryClient();
const onFlowNameUpdate = async (name) => {
await updateFlow({
@@ -38,6 +41,24 @@ export default function EditorLayout() {
});
};
const onFlowStatusUpdate = React.useCallback(
async (active) => {
try {
await updateFlowStatus({
variables: {
input: {
id: flowId,
active,
},
},
});
await queryClient.invalidateQueries({ queryKey: ['flows', flowId] });
} catch (err) {}
},
[flowId, queryClient],
);
return (
<>
<TopBar
@@ -86,7 +107,7 @@ export default function EditorLayout() {
disabled={!allowed || !flow}
variant="contained"
size="small"
onClick={() => updateFlowStatus(!flow.active)}
onClick={() => onFlowStatusUpdate(!flow.active)}
data-test={
flow?.active ? 'unpublish-flow-button' : 'publish-flow-button'
}
@@ -132,7 +153,7 @@ export default function EditorLayout() {
<Button
variant="contained"
size="small"
onClick={() => updateFlowStatus(!flow.active)}
onClick={() => onFlowStatusUpdate(!flow.active)}
data-test="unpublish-flow-from-snackbar"
>
{formatMessage('flowEditor.unpublish')}

View File

@@ -5,8 +5,8 @@ import { FlowPropType } from 'propTypes/propTypes';
import ReactFlow, { useNodesState, useEdgesState } from 'reactflow';
import 'reactflow/dist/style.css';
import { UPDATE_STEP } from 'graphql/mutations/update-step';
import useCreateStep from 'hooks/useCreateStep';
import useUpdateStep from 'hooks/useUpdateStep';
import { useAutoLayout } from './useAutoLayout';
import { useScrollBoundaries } from './useScrollBoundaries';
@@ -35,7 +35,7 @@ const edgeTypes = {
};
const EditorNew = ({ flow }) => {
const { mutateAsync: updateStep } = useUpdateStep();
const [updateStep] = useMutation(UPDATE_STEP);
const queryClient = useQueryClient();
const { mutateAsync: createStep, isPending: isCreateStepPending } =
@@ -84,24 +84,31 @@ const EditorNew = ({ flow }) => {
const onStepChange = useCallback(
async (step) => {
const payload = {
const mutationInput = {
id: step.id,
key: step.key,
parameters: step.parameters,
connectionId: step.connection?.id,
connection: {
id: step.connection?.id,
},
flow: {
id: flow.id,
},
};
if (step.appKey) {
payload.appKey = step.appKey;
mutationInput.appKey = step.appKey;
}
await updateStep(payload);
await updateStep({
variables: { input: mutationInput },
});
await queryClient.invalidateQueries({
queryKey: ['steps', step.id, 'connection'],
});
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
},
[updateStep, queryClient],
[flow.id, updateStep, queryClient],
);
const onAddStep = useCallback(

View File

@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
import { useMutation } from '@apollo/client';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import { useQueryClient } from '@tanstack/react-query';
@@ -9,9 +10,9 @@ import { Link } from 'react-router-dom';
import Can from 'components/Can';
import * as URLS from 'config/urls';
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
import useFormatMessage from 'hooks/useFormatMessage';
import useDuplicateFlow from 'hooks/useDuplicateFlow';
import useDeleteFlow from 'hooks/useDeleteFlow';
function ContextMenu(props) {
const { flowId, onClose, anchorEl, onDuplicateFlow, onDeleteFlow, appKey } =
@@ -20,7 +21,7 @@ function ContextMenu(props) {
const formatMessage = useFormatMessage();
const queryClient = useQueryClient();
const { mutateAsync: duplicateFlow } = useDuplicateFlow(flowId);
const { mutateAsync: deleteFlow } = useDeleteFlow();
const [deleteFlow] = useMutation(DELETE_FLOW);
const onFlowDuplicate = React.useCallback(async () => {
await duplicateFlow();
@@ -51,7 +52,18 @@ function ContextMenu(props) {
]);
const onFlowDelete = React.useCallback(async () => {
await deleteFlow(flowId);
await deleteFlow({
variables: { input: { id: flowId } },
update: (cache) => {
const flowCacheId = cache.identify({
__typename: 'Flow',
id: flowId,
});
cache.evict({
id: flowCacheId,
});
},
});
if (appKey) {
await queryClient.invalidateQueries({

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useMutation } from '@apollo/client';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import useDeleteStep from 'hooks/useDeleteStep';
import { DELETE_STEP } from 'graphql/mutations/delete-step';
import useFormatMessage from 'hooks/useFormatMessage';
import { useQueryClient } from '@tanstack/react-query';
@@ -12,17 +13,15 @@ function FlowStepContextMenu(props) {
const { stepId, onClose, anchorEl, deletable, flowId } = props;
const formatMessage = useFormatMessage();
const queryClient = useQueryClient();
const { mutateAsync: deleteStep } = useDeleteStep();
const [deleteStep] = useMutation(DELETE_STEP);
const deleteActionHandler = React.useCallback(
async (event) => {
event.stopPropagation();
await deleteStep(stepId);
await deleteStep({ variables: { input: { id: stepId } } });
await queryClient.invalidateQueries({ queryKey: ['flows', flowId] });
},
[deleteStep, stepId, queryClient, flowId],
[stepId, queryClient],
);
return (

View File

@@ -21,7 +21,7 @@ function NoResultFound(props) {
);
return (
<Card elevation={0} data-test="no-results">
<Card elevation={0}>
<CardActionArea component={ActionAreaLink} {...props}>
<CardContent>
{!!to && <AddCircleIcon color="primary" />}

View File

@@ -42,7 +42,6 @@ function Switch(props) {
{...FormControlLabelProps}
control={
<MuiSwitch
data-test="switch"
{...switchProps}
{...field}
checked={value}

View File

@@ -41,6 +41,19 @@ export const APP_FLOWS_FOR_CONNECTION = (appKey, connectionId) =>
export const APP_FLOWS_PATTERN = '/app/:appKey/flows';
export const EDITOR = '/editor';
export const CREATE_FLOW = '/editor/create';
export const CREATE_FLOW_WITH_APP = (appKey) =>
`/editor/create?appKey=${appKey}`;
export const CREATE_FLOW_WITH_APP_AND_CONNECTION = (appKey, connectionId) => {
const params = {};
if (appKey) {
params.appKey = appKey;
}
if (connectionId) {
params.connectionId = connectionId;
}
const searchParams = new URLSearchParams(params).toString();
return `/editor/create?${searchParams}`;
};
export const FLOW_EDITOR = (flowId) => `/editor/${flowId}`;
export const FLOWS = '/flows';
// TODO: revert this back to /flows/:flowId once we have a proper single flow page
@@ -87,4 +100,5 @@ export const DASHBOARD = FLOWS;
// External links and paths
// The paths are sensitive for their relativity.
export const WEBHOOK_DOCS_PATH = './apps/webhooks/connection';
export const WEBHOOK_DOCS_PATH =
'./apps/webhooks/connection';

View File

@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const CREATE_FLOW = gql`
mutation CreateFlow($input: CreateFlowInput) {
createFlow(input: $input) {
id
name
}
}
`;

View File

@@ -0,0 +1,16 @@
import { gql } from '@apollo/client';
export const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput) {
createUser(input: $input) {
user {
id
email
fullName
role {
id
}
}
acceptInvitationUrl
}
}
`;

View File

@@ -0,0 +1,6 @@
import { gql } from '@apollo/client';
export const DELETE_CURRENT_USER = gql`
mutation DeleteCurrentUser {
deleteCurrentUser
}
`;

View File

@@ -0,0 +1,6 @@
import { gql } from '@apollo/client';
export const DELETE_FLOW = gql`
mutation DeleteFlow($input: DeleteFlowInput) {
deleteFlow(input: $input)
}
`;

View File

@@ -0,0 +1,14 @@
import { gql } from '@apollo/client';
export const DELETE_STEP = gql`
mutation DeleteStep($input: DeleteStepInput) {
deleteStep(input: $input) {
id
flow {
id
steps {
id
}
}
}
}
`;

View File

@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const UPDATE_FLOW_STATUS = gql`
mutation UpdateFlowStatus($input: UpdateFlowStatusInput) {
updateFlowStatus(input: $input) {
id
active
}
}
`;

View File

@@ -0,0 +1,17 @@
import { gql } from '@apollo/client';
export const UPDATE_STEP = gql`
mutation UpdateStep($input: UpdateStepInput) {
updateStep(input: $input) {
id
type
key
appKey
parameters
status
webhookUrl
connection {
id
}
}
}
`;

View File

@@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const UPDATE_USER = gql`
mutation UpdateUser($input: UpdateUserInput) {
updateUser(input: $input) {
id
email
fullName
}
}
`;

View File

@@ -12,7 +12,7 @@ export default function useAdminCreateRole() {
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['admin', 'roles'],
queryKey: ['apps', 'roles'],
});
},
});

View File

@@ -1,21 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useAdminCreateUser() {
const queryClient = useQueryClient();
const query = useMutation({
mutationFn: async (payload) => {
const { data } = await api.post('/v1/admin/users', payload);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['admin', 'users'],
});
},
});
return query;
}

View File

@@ -1,34 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import useFormatMessage from 'hooks/useFormatMessage';
export default function useAdminUpdateUser(userId) {
const queryClient = useQueryClient();
const enqueueSnackbar = useEnqueueSnackbar();
const formatMessage = useFormatMessage();
const query = useMutation({
mutationFn: async (payload) => {
const { data } = await api.patch(`/v1/admin/users/${userId}`, payload);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['admin', 'users'],
});
},
onError: () => {
enqueueSnackbar(formatMessage('editUser.error'), {
variant: 'error',
persist: true,
SnackbarProps: {
'data-test': 'snackbar-error',
},
});
},
});
return query;
}

View File

@@ -1,23 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useCreateFlow() {
const queryClient = useQueryClient();
const query = useMutation({
mutationFn: async () => {
const { data } = await api.post('/v1/flows');
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['flows'],
});
},
});
return query;
}

View File

@@ -1,14 +0,0 @@
import { useMutation } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useDeleteCurrentUser(userId) {
const query = useMutation({
mutationFn: async () => {
const { data } = await api.delete(`/v1/users/${userId}`);
return data;
},
});
return query;
}

View File

@@ -1,23 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useDeleteFlow() {
const queryClient = useQueryClient();
const query = useMutation({
mutationFn: async (flowId) => {
const { data } = await api.delete(`/v1/flows/${flowId}`);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['flows'],
});
},
});
return query;
}

View File

@@ -1,14 +0,0 @@
import { useMutation } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useDeleteStep() {
const query = useMutation({
mutationFn: async (stepId) => {
const { data } = await api.delete(`/v1/steps/${stepId}`);
return data;
},
});
return query;
}

View File

@@ -1,39 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
export default function useUpdateFlowStatus(flowId) {
const queryClient = useQueryClient();
const enqueueSnackbar = useEnqueueSnackbar();
const query = useMutation({
mutationFn: async (active) => {
const { data } = await api.patch(`/v1/flows/${flowId}/status`, {
active,
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['flows', flowId],
});
},
onError: (error) => {
const errors = Object.values(
error.response.data.errors || [['Failed while updating flow status!']],
);
for (const [error] of errors) {
enqueueSnackbar(error, {
variant: 'error',
SnackbarProps: {
'data-test': 'snackbar-error',
},
});
}
}
});
return query;
}

View File

@@ -1,28 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from 'helpers/api';
export default function useUpdateStep() {
const queryClient = useQueryClient();
const query = useMutation({
mutationFn: async ({ id, appKey, key, connectionId, parameters }) => {
const { data } = await api.patch(`/v1/steps/${id}`, {
appKey,
key,
connectionId,
parameters,
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['flows'],
});
},
});
return query;
}

View File

@@ -223,12 +223,10 @@
"createUser.submit": "Create",
"createUser.successfullyCreated": "The user has been created.",
"createUser.invitationEmailInfo": "Invitation email will be sent if SMTP credentials are valid. Otherwise, you can share the invitation link manually: <link></link>",
"createUser.error": "Error while creating the user.",
"editUserPage.title": "Edit user",
"editUser.status": "Status",
"editUser.submit": "Update",
"editUser.successfullyUpdated": "The user has been updated.",
"editUser.error": "Error while updating the user.",
"userList.fullName": "Full name",
"userList.email": "Email",
"userList.role": "Role",

View File

@@ -134,7 +134,10 @@ export default function Application() {
color="primary"
size="large"
component={Link}
to={URLS.CREATE_FLOW}
to={URLS.CREATE_FLOW_WITH_APP_AND_CONNECTION(
appKey,
connectionId,
)}
fullWidth
icon={<AddIcon />}
disabled={!allowed}

View File

@@ -1,3 +1,4 @@
import { useMutation } from '@apollo/client';
import LoadingButton from '@mui/lab/LoadingButton';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
@@ -13,9 +14,9 @@ import ControlledAutocomplete from 'components/ControlledAutocomplete';
import Form from 'components/Form';
import PageTitle from 'components/PageTitle';
import TextField from 'components/TextField';
import { CREATE_USER } from 'graphql/mutations/create-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee';
import useAdminCreateUser from 'hooks/useAdminCreateUser';
function generateRoleOptions(roles) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
@@ -23,11 +24,7 @@ function generateRoleOptions(roles) {
export default function CreateUser() {
const formatMessage = useFormatMessage();
const {
mutateAsync: createUser,
isPending: isCreateUserPending,
data: createdUser,
} = useAdminCreateUser();
const [createUser, { loading, data }] = useMutation(CREATE_USER);
const { data: rolesData, loading: isRolesLoading } = useRoles();
const roles = rolesData?.data;
const enqueueSnackbar = useEnqueueSnackbar();
@@ -36,13 +33,17 @@ export default function CreateUser() {
const handleUserCreation = async (userData) => {
try {
await createUser({
fullName: userData.fullName,
email: userData.email,
roleId: userData.role?.id,
variables: {
input: {
fullName: userData.fullName,
email: userData.email,
role: {
id: userData.role?.id,
},
},
},
});
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
enqueueSnackbar(formatMessage('createUser.successfullyCreated'), {
variant: 'success',
persist: true,
@@ -51,14 +52,6 @@ export default function CreateUser() {
},
});
} catch (error) {
enqueueSnackbar(formatMessage('createUser.error'), {
variant: 'error',
persist: true,
SnackbarProps: {
'data-test': 'snackbar-error',
},
});
throw new Error('Failed while creating!');
}
};
@@ -114,13 +107,13 @@ export default function CreateUser() {
variant="contained"
color="primary"
sx={{ boxShadow: 2 }}
loading={isCreateUserPending}
loading={loading}
data-test="create-button"
>
{formatMessage('createUser.submit')}
</LoadingButton>
{createdUser && (
{data && (
<Alert
severity="info"
color="primary"
@@ -130,11 +123,11 @@ export default function CreateUser() {
{formatMessage('createUser.invitationEmailInfo', {
link: () => (
<a
href={createdUser.data.acceptInvitationUrl}
href={data.createUser.acceptInvitationUrl}
target="_blank"
rel="noreferrer"
>
{createdUser.data.acceptInvitationUrl}
{data.createUser.acceptInvitationUrl}
</a>
),
})}

View File

@@ -1,3 +1,4 @@
import { useMutation } from '@apollo/client';
import LoadingButton from '@mui/lab/LoadingButton';
import Grid from '@mui/material/Grid';
import Skeleton from '@mui/material/Skeleton';
@@ -8,6 +9,7 @@ import MuiTextField from '@mui/material/TextField';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import Can from 'components/Can';
import Container from 'components/Container';
@@ -16,9 +18,9 @@ import Form from 'components/Form';
import PageTitle from 'components/PageTitle';
import TextField from 'components/TextField';
import * as URLS from 'config/urls';
import { UPDATE_USER } from 'graphql/mutations/update-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee';
import useAdminUpdateUser from 'hooks/useAdminUpdateUser';
import useAdminUser from 'hooks/useAdminUser';
function generateRoleOptions(roles) {
@@ -27,23 +29,31 @@ function generateRoleOptions(roles) {
export default function EditUser() {
const formatMessage = useFormatMessage();
const [updateUser, { loading }] = useMutation(UPDATE_USER);
const { userId } = useParams();
const { mutateAsync: updateUser, isPending: isAdminUpdateUserPending } =
useAdminUpdateUser(userId);
const { data: userData, isLoading: isUserLoading } = useAdminUser({ userId });
const user = userData?.data;
const { data, isLoading: isRolesLoading } = useRoles();
const roles = data?.data;
const enqueueSnackbar = useEnqueueSnackbar();
const navigate = useNavigate();
const queryClient = useQueryClient();
const handleUserUpdate = async (userDataToUpdate) => {
try {
await updateUser({
fullName: userDataToUpdate.fullName,
email: userDataToUpdate.email,
roleId: userDataToUpdate.role?.id,
variables: {
input: {
id: userId,
fullName: userDataToUpdate.fullName,
email: userDataToUpdate.email,
role: {
id: userDataToUpdate.role?.id,
},
},
},
});
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
enqueueSnackbar(formatMessage('editUser.successfullyUpdated'), {
variant: 'success',
@@ -132,7 +142,7 @@ export default function EditUser() {
variant="contained"
color="primary"
sx={{ boxShadow: 2 }}
loading={isAdminUpdateUserPending}
loading={loading}
data-test="update-button"
>
{formatMessage('editUser.submit')}

View File

@@ -1,30 +1,42 @@
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useCreateFlow from 'hooks/useCreateFlow';
import { CREATE_FLOW } from 'graphql/mutations/create-flow';
import Box from '@mui/material/Box';
export default function CreateFlow() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const { mutateAsync: createFlow, isError } = useCreateFlow();
const [createFlow, { error }] = useMutation(CREATE_FLOW);
const appKey = searchParams.get('appKey');
const connectionId = searchParams.get('connectionId');
React.useEffect(() => {
async function initiate() {
const response = await createFlow();
const flowId = response.data?.id;
const variables = {};
if (appKey) {
variables.triggerAppKey = appKey;
}
if (connectionId) {
variables.connectionId = connectionId;
}
const response = await createFlow({
variables: {
input: variables,
},
});
const flowId = response.data?.createFlow?.id;
navigate(URLS.FLOW_EDITOR(flowId), { replace: true });
}
initiate();
}, [createFlow, navigate]);
}, [createFlow, navigate, appKey, connectionId]);
if (isError) {
if (error) {
return null;
}

445
yarn.lock
View File

@@ -1629,130 +1629,130 @@
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@esbuild/aix-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.15.11":
version "0.15.11"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.11.tgz#bdd9c3e098183bdca97075aa4c3e0152ed3e10ee"
integrity sha512-PzMcQLazLBkwDEkrNPi9AbjFt6+3I7HKbiYF2XtWQ7wItrHvEOeO3T8Am434zAozWtVP7lrTue1bEfc2nYWeCA==
"@esbuild/android-arm@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.15.11":
version "0.15.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.11.tgz#2f4f9a1083dcb4fc65233b6f59003c406abf32e5"
integrity sha512-geWp637tUhNmhL3Xgy4Bj703yXB9dqiLJe05lCUfjSFDrQf9C/8pArusyPUbUbPwlC/EAUjBw32sxuIl/11dZw==
"@esbuild/linux-loong64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
@@ -3459,80 +3459,85 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@rollup/rollup-android-arm-eabi@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz#57936f50d0335e2e7bfac496d209606fa516add4"
integrity sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==
"@rollup/rollup-android-arm-eabi@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz#155c7d82c1b36c3ad84d9adf9b3cd520cba81a0f"
integrity sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==
"@rollup/rollup-android-arm64@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz#81bba83b37382a2d0e30ceced06c8d3d85138054"
integrity sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==
"@rollup/rollup-android-arm64@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz#b94b6fa002bd94a9cbd8f9e47e23b25e5bd113ba"
integrity sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==
"@rollup/rollup-darwin-arm64@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz#a371bd723a5c4c4a33376da72abfc3938066842b"
integrity sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==
"@rollup/rollup-darwin-arm64@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz#0934126cf9cbeadfe0eb7471ab5d1517e8cd8dcc"
integrity sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==
"@rollup/rollup-darwin-x64@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz#8baf2fda277c9729125017c65651296282412886"
integrity sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==
"@rollup/rollup-darwin-x64@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz#0ce8e1e0f349778938c7c90e4bdc730640e0a13e"
integrity sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==
"@rollup/rollup-linux-arm-gnueabihf@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz#822830a8f7388d5b81d04c69415408d3bab1079b"
integrity sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==
"@rollup/rollup-linux-arm-gnueabihf@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz#5669d34775ad5d71e4f29ade99d0ff4df523afb6"
integrity sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==
"@rollup/rollup-linux-arm64-gnu@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz#e20fbe1bd4414c7119f9e0bba8ad17a6666c8365"
integrity sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==
"@rollup/rollup-linux-arm-musleabihf@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz#f6d1a0e1da4061370cb2f4244fbdd727c806dd88"
integrity sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==
"@rollup/rollup-linux-arm64-musl@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz#13f475596a62e1924f13fe1c8cf2c40e09a99b47"
integrity sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==
"@rollup/rollup-linux-arm64-gnu@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz#ed96a05e99743dee4d23cc4913fc6e01a0089c88"
integrity sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==
"@rollup/rollup-linux-powerpc64le-gnu@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz#6a431c441420d1c510a205e08c6673355a0a2ea9"
integrity sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==
"@rollup/rollup-linux-arm64-musl@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz#057ea26eaa7e537a06ded617d23d57eab3cecb58"
integrity sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==
"@rollup/rollup-linux-riscv64-gnu@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz#53d9448962c3f9ed7a1672269655476ea2d67567"
integrity sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==
"@rollup/rollup-linux-powerpc64le-gnu@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz#6e6e1f9404c9bf3fbd7d51cd11cd288a9a2843aa"
integrity sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==
"@rollup/rollup-linux-s390x-gnu@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz#95f0c133b324da3e7e5c7d12855e0eb71d21a946"
integrity sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==
"@rollup/rollup-linux-riscv64-gnu@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz#eef1536a53f6e6658a2a778130e6b1a4a41cb439"
integrity sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==
"@rollup/rollup-linux-x64-gnu@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz#820ada75c68ead1acc486e41238ca0d8f8531478"
integrity sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==
"@rollup/rollup-linux-s390x-gnu@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz#2b28fb89ca084efaf8086f435025d96b4a966957"
integrity sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==
"@rollup/rollup-linux-x64-musl@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz#ca74f22e125efbe94c1148d989ef93329b464443"
integrity sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==
"@rollup/rollup-linux-x64-gnu@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz#5226cde6c6b495b04a3392c1d2c572844e42f06b"
integrity sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==
"@rollup/rollup-win32-arm64-msvc@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz#269023332297051d037a9593dcba92c10fef726b"
integrity sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==
"@rollup/rollup-linux-x64-musl@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz#2c2412982e6c2a00a2ecac6d548ebb02f0aa6ca4"
integrity sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==
"@rollup/rollup-win32-ia32-msvc@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz#d7701438daf964011fd7ca33e3f13f3ff5129e7b"
integrity sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==
"@rollup/rollup-win32-arm64-msvc@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz#fbb6ef5379199e2ec0103ef32877b0985c773a55"
integrity sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==
"@rollup/rollup-win32-x64-msvc@4.14.0":
version "4.14.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz#0bb7ac3cd1c3292db1f39afdabfd03ccea3a3d34"
integrity sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==
"@rollup/rollup-win32-ia32-msvc@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz#d50e2082e147e24d87fe34abbf6246525ec3845a"
integrity sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==
"@rollup/rollup-win32-x64-msvc@4.21.3":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz#4115233aa1bd5a2060214f96d8511f6247093212"
integrity sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==
"@rudderstack/rudder-sdk-node@^1.1.2":
version "1.1.2"
@@ -8035,34 +8040,34 @@ esbuild@^0.15.9:
esbuild-windows-64 "0.15.11"
esbuild-windows-arm64 "0.15.11"
esbuild@^0.20.1:
version "0.20.2"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
optionalDependencies:
"@esbuild/aix-ppc64" "0.20.2"
"@esbuild/android-arm" "0.20.2"
"@esbuild/android-arm64" "0.20.2"
"@esbuild/android-x64" "0.20.2"
"@esbuild/darwin-arm64" "0.20.2"
"@esbuild/darwin-x64" "0.20.2"
"@esbuild/freebsd-arm64" "0.20.2"
"@esbuild/freebsd-x64" "0.20.2"
"@esbuild/linux-arm" "0.20.2"
"@esbuild/linux-arm64" "0.20.2"
"@esbuild/linux-ia32" "0.20.2"
"@esbuild/linux-loong64" "0.20.2"
"@esbuild/linux-mips64el" "0.20.2"
"@esbuild/linux-ppc64" "0.20.2"
"@esbuild/linux-riscv64" "0.20.2"
"@esbuild/linux-s390x" "0.20.2"
"@esbuild/linux-x64" "0.20.2"
"@esbuild/netbsd-x64" "0.20.2"
"@esbuild/openbsd-x64" "0.20.2"
"@esbuild/sunos-x64" "0.20.2"
"@esbuild/win32-arm64" "0.20.2"
"@esbuild/win32-ia32" "0.20.2"
"@esbuild/win32-x64" "0.20.2"
"@esbuild/aix-ppc64" "0.21.5"
"@esbuild/android-arm" "0.21.5"
"@esbuild/android-arm64" "0.21.5"
"@esbuild/android-x64" "0.21.5"
"@esbuild/darwin-arm64" "0.21.5"
"@esbuild/darwin-x64" "0.21.5"
"@esbuild/freebsd-arm64" "0.21.5"
"@esbuild/freebsd-x64" "0.21.5"
"@esbuild/linux-arm" "0.21.5"
"@esbuild/linux-arm64" "0.21.5"
"@esbuild/linux-ia32" "0.21.5"
"@esbuild/linux-loong64" "0.21.5"
"@esbuild/linux-mips64el" "0.21.5"
"@esbuild/linux-ppc64" "0.21.5"
"@esbuild/linux-riscv64" "0.21.5"
"@esbuild/linux-s390x" "0.21.5"
"@esbuild/linux-x64" "0.21.5"
"@esbuild/netbsd-x64" "0.21.5"
"@esbuild/openbsd-x64" "0.21.5"
"@esbuild/sunos-x64" "0.21.5"
"@esbuild/win32-arm64" "0.21.5"
"@esbuild/win32-ia32" "0.21.5"
"@esbuild/win32-x64" "0.21.5"
escalade@^3.1.1:
version "3.1.1"
@@ -13073,6 +13078,11 @@ picocolors@^1.0.0:
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picocolors@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59"
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3:
version "2.3.1"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
@@ -13690,14 +13700,14 @@ postcss@^8.4.18:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
postcss@^8.4.43:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.2.0"
picocolors "^1.1.0"
source-map-js "^1.2.1"
postgres-array@~2.0.0:
version "2.0.0"
@@ -14763,28 +14773,29 @@ rollup@^2.79.1:
optionalDependencies:
fsevents "~2.3.2"
rollup@^4.13.0:
version "4.14.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.0.tgz#c3e2cd479f1b2358b65c1f810fa05b51603d7be8"
integrity sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==
rollup@^4.20.0:
version "4.21.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.3.tgz#c64ba119e6aeb913798a6f7eef2780a0df5a0821"
integrity sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==
dependencies:
"@types/estree" "1.0.5"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.14.0"
"@rollup/rollup-android-arm64" "4.14.0"
"@rollup/rollup-darwin-arm64" "4.14.0"
"@rollup/rollup-darwin-x64" "4.14.0"
"@rollup/rollup-linux-arm-gnueabihf" "4.14.0"
"@rollup/rollup-linux-arm64-gnu" "4.14.0"
"@rollup/rollup-linux-arm64-musl" "4.14.0"
"@rollup/rollup-linux-powerpc64le-gnu" "4.14.0"
"@rollup/rollup-linux-riscv64-gnu" "4.14.0"
"@rollup/rollup-linux-s390x-gnu" "4.14.0"
"@rollup/rollup-linux-x64-gnu" "4.14.0"
"@rollup/rollup-linux-x64-musl" "4.14.0"
"@rollup/rollup-win32-arm64-msvc" "4.14.0"
"@rollup/rollup-win32-ia32-msvc" "4.14.0"
"@rollup/rollup-win32-x64-msvc" "4.14.0"
"@rollup/rollup-android-arm-eabi" "4.21.3"
"@rollup/rollup-android-arm64" "4.21.3"
"@rollup/rollup-darwin-arm64" "4.21.3"
"@rollup/rollup-darwin-x64" "4.21.3"
"@rollup/rollup-linux-arm-gnueabihf" "4.21.3"
"@rollup/rollup-linux-arm-musleabihf" "4.21.3"
"@rollup/rollup-linux-arm64-gnu" "4.21.3"
"@rollup/rollup-linux-arm64-musl" "4.21.3"
"@rollup/rollup-linux-powerpc64le-gnu" "4.21.3"
"@rollup/rollup-linux-riscv64-gnu" "4.21.3"
"@rollup/rollup-linux-s390x-gnu" "4.21.3"
"@rollup/rollup-linux-x64-gnu" "4.21.3"
"@rollup/rollup-linux-x64-musl" "4.21.3"
"@rollup/rollup-win32-arm64-msvc" "4.21.3"
"@rollup/rollup-win32-ia32-msvc" "4.21.3"
"@rollup/rollup-win32-x64-msvc" "4.21.3"
fsevents "~2.3.2"
run-async@^2.4.0:
@@ -15287,10 +15298,10 @@ source-map-js@^1.0.1, source-map-js@^1.0.2:
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-loader@^3.0.0:
version "3.0.1"
@@ -16620,9 +16631,9 @@ vite-node@1.1.3:
vite "^5.0.0"
vite@^3.1.6:
version "3.2.10"
resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.10.tgz#7ac79fead82cfb6b5bf65613cd82fba6dcc81340"
integrity sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==
version "3.2.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.11.tgz#8d1c8e05ef2f24b04c8693f56d3e01fe8835e6d7"
integrity sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==
dependencies:
esbuild "^0.15.9"
postcss "^8.4.18"
@@ -16632,13 +16643,13 @@ vite@^3.1.6:
fsevents "~2.3.2"
vite@^5.0.0:
version "5.2.8"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa"
integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==
version "5.4.6"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.6.tgz#85a93a1228a7fb5a723ca1743e337a2588ed008f"
integrity sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==
dependencies:
esbuild "^0.20.1"
postcss "^8.4.38"
rollup "^4.13.0"
esbuild "^0.21.3"
postcss "^8.4.43"
rollup "^4.20.0"
optionalDependencies:
fsevents "~2.3.3"