feat: add new commit trigger in GitHub
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
import ListRepos from './data/list-repos';
|
import ListRepos from './data/list-repos';
|
||||||
|
import ListBranches from './data/list-branches';
|
||||||
|
|
||||||
export default class Data {
|
export default class Data {
|
||||||
listRepos: ListRepos;
|
listRepos: ListRepos;
|
||||||
|
listBranches: ListBranches;
|
||||||
|
|
||||||
constructor(connectionData: IJSONObject) {
|
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||||
this.listRepos = new ListRepos(connectionData);
|
this.listRepos = new ListRepos(connectionData);
|
||||||
|
this.listBranches = new ListBranches(connectionData, parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
packages/backend/src/apps/github/data/list-branches.ts
Normal file
36
packages/backend/src/apps/github/data/list-branches.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Octokit } from 'octokit';
|
||||||
|
import type { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
|
export default class ListBranches {
|
||||||
|
client?: Octokit;
|
||||||
|
repoOwner?: string;
|
||||||
|
repo?: string;
|
||||||
|
|
||||||
|
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
|
||||||
|
if (connectionData.accessToken) {
|
||||||
|
this.client = new Octokit({
|
||||||
|
auth: connectionData.accessToken as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
get options() {
|
||||||
|
return {
|
||||||
|
owner: this.repoOwner,
|
||||||
|
repo: this.repo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const branches = await this.client.paginate(this.client.rest.repos.listBranches, this.options);
|
||||||
|
|
||||||
|
return branches.map((branch) => ({
|
||||||
|
value: branch.name,
|
||||||
|
name: branch.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,7 @@ export default class Github implements IService {
|
|||||||
parameters: IJSONObject
|
parameters: IJSONObject
|
||||||
) {
|
) {
|
||||||
this.authenticationClient = new Authentication(appData, connectionData);
|
this.authenticationClient = new Authentication(appData, connectionData);
|
||||||
this.data = new Data(connectionData);
|
this.data = new Data(connectionData, parameters);
|
||||||
this.triggers = new Triggers(connectionData, parameters);
|
this.triggers = new Triggers(connectionData, parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -599,6 +599,68 @@
|
|||||||
"name": "Test trigger"
|
"name": "Test trigger"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "New commit",
|
||||||
|
"key": "newCommit",
|
||||||
|
"interval": "15m",
|
||||||
|
"description": "Triggers when a new commit is created",
|
||||||
|
"substeps": [
|
||||||
|
{
|
||||||
|
"key": "chooseAccount",
|
||||||
|
"name": "Choose account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "chooseTrigger",
|
||||||
|
"name": "Set up a trigger",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"label": "Repo",
|
||||||
|
"key": "repo",
|
||||||
|
"type": "dropdown",
|
||||||
|
"required": true,
|
||||||
|
"variables": false,
|
||||||
|
"source": {
|
||||||
|
"type": "query",
|
||||||
|
"name": "getData",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"value": "listRepos"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Head",
|
||||||
|
"key": "head",
|
||||||
|
"type": "dropdown",
|
||||||
|
"description": "Branch to pull commits from. If unspecified, will use the repository's default branch (usually main or develop).",
|
||||||
|
"required": false,
|
||||||
|
"variables": false,
|
||||||
|
"dependsOn": ["parameters.repo"],
|
||||||
|
"source": {
|
||||||
|
"type": "query",
|
||||||
|
"name": "getData",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"value": "listBranches"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameters.repo",
|
||||||
|
"value": "{parameters.repo}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "testStep",
|
||||||
|
"name": "Test trigger"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import NewNotification from './triggers/new-notification';
|
|||||||
import NewPullRequest from './triggers/new-pull-request';
|
import NewPullRequest from './triggers/new-pull-request';
|
||||||
import NewWatcher from './triggers/new-watcher';
|
import NewWatcher from './triggers/new-watcher';
|
||||||
import NewMilestone from './triggers/new-milestone';
|
import NewMilestone from './triggers/new-milestone';
|
||||||
|
import NewCommit from './triggers/new-commit';
|
||||||
import NewCommitComment from './triggers/new-commit-comment';
|
import NewCommitComment from './triggers/new-commit-comment';
|
||||||
import NewLabel from './triggers/new-label';
|
import NewLabel from './triggers/new-label';
|
||||||
import NewCollaborator from './triggers/new-collaborator';
|
import NewCollaborator from './triggers/new-collaborator';
|
||||||
@@ -19,6 +20,7 @@ export default class Triggers {
|
|||||||
newPullRequest: NewPullRequest;
|
newPullRequest: NewPullRequest;
|
||||||
newWatcher: NewWatcher;
|
newWatcher: NewWatcher;
|
||||||
newMilestone: NewMilestone;
|
newMilestone: NewMilestone;
|
||||||
|
newCommit: NewCommit;
|
||||||
newCommitComment: NewCommitComment;
|
newCommitComment: NewCommitComment;
|
||||||
newLabel: NewLabel;
|
newLabel: NewLabel;
|
||||||
newCollaborator: NewCollaborator;
|
newCollaborator: NewCollaborator;
|
||||||
@@ -32,6 +34,7 @@ export default class Triggers {
|
|||||||
this.newPullRequest = new NewPullRequest(connectionData, parameters);
|
this.newPullRequest = new NewPullRequest(connectionData, parameters);
|
||||||
this.newWatcher = new NewWatcher(connectionData, parameters);
|
this.newWatcher = new NewWatcher(connectionData, parameters);
|
||||||
this.newMilestone = new NewMilestone(connectionData, parameters);
|
this.newMilestone = new NewMilestone(connectionData, parameters);
|
||||||
|
this.newCommit = new NewCommit(connectionData, parameters);
|
||||||
this.newCommitComment = new NewCommitComment(connectionData, parameters);
|
this.newCommitComment = new NewCommitComment(connectionData, parameters);
|
||||||
this.newLabel = new NewLabel(connectionData, parameters);
|
this.newLabel = new NewLabel(connectionData, parameters);
|
||||||
this.newCollaborator = new NewCollaborator(connectionData, parameters);
|
this.newCollaborator = new NewCollaborator(connectionData, parameters);
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { Octokit } from 'octokit';
|
import { Octokit } from 'octokit';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewBranch {
|
export default class NewBranch {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -13,12 +15,7 @@ export default class NewBranch {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { Octokit } from 'octokit';
|
import { Octokit } from 'octokit';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewCollaborator {
|
export default class NewCollaborator {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -13,12 +15,7 @@ export default class NewCollaborator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -2,6 +2,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewCommitComment {
|
export default class NewCommitComment {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -14,12 +16,7 @@ export default class NewCommitComment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
59
packages/backend/src/apps/github/triggers/new-commit.ts
Normal file
59
packages/backend/src/apps/github/triggers/new-commit.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Octokit } from 'octokit';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
|
export default class NewCommit {
|
||||||
|
client?: Octokit;
|
||||||
|
repoOwner?: string;
|
||||||
|
repo?: string;
|
||||||
|
head?: string;
|
||||||
|
|
||||||
|
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||||
|
if (connectionData.accessToken) {
|
||||||
|
this.client = new Octokit({
|
||||||
|
auth: connectionData.accessToken as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters?.head) {
|
||||||
|
this.head = parameters.head as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
get options() {
|
||||||
|
const options = {
|
||||||
|
owner: this.repoOwner,
|
||||||
|
repo: this.repo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.head) {
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
sha: this.head,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(startTime: Date) {
|
||||||
|
const options = {
|
||||||
|
...this.options,
|
||||||
|
since: DateTime.fromJSDate(startTime).toISO(),
|
||||||
|
};
|
||||||
|
return await this.client.paginate(this.client.rest.repos.listCommits, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async testRun() {
|
||||||
|
const options = {
|
||||||
|
...this.options,
|
||||||
|
per_page: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (await this.client.rest.repos.listCommits(options)).data;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
import { Octokit } from 'octokit';
|
import { Octokit } from 'octokit';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewLabel {
|
export default class NewLabel {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -13,12 +15,7 @@ export default class NewLabel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -2,6 +2,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewMilestone {
|
export default class NewMilestone {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -14,12 +16,7 @@ export default class NewMilestone {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -2,6 +2,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewNotification {
|
export default class NewNotification {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
connectionData?: IJSONObject;
|
connectionData?: IJSONObject;
|
||||||
@@ -19,12 +21,7 @@ export default class NewNotification {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasRepo() {
|
get hasRepo() {
|
||||||
|
@@ -2,6 +2,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewPullRequest {
|
export default class NewPullRequest {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -14,12 +16,7 @@ export default class NewPullRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -2,6 +2,8 @@ import { Octokit } from 'octokit';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewRelease {
|
export default class NewRelease {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -14,12 +16,7 @@ export default class NewRelease {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = 'facebook' || owner;
|
|
||||||
this.repo = 'react' || repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { Octokit } from 'octokit';
|
import { Octokit } from 'octokit';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { assignOwnerAndRepo } from '../utils';
|
||||||
|
|
||||||
export default class NewWatcher {
|
export default class NewWatcher {
|
||||||
client?: Octokit;
|
client?: Octokit;
|
||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
@@ -13,12 +15,7 @@ export default class NewWatcher {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters?.repo) {
|
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||||
const [owner, repo] = (parameters.repo as string).split('/');
|
|
||||||
|
|
||||||
this.repoOwner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
9
packages/backend/src/apps/github/utils.ts
Normal file
9
packages/backend/src/apps/github/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; }>(object: T, repoFullName: string): T {
|
||||||
|
if (object && repoFullName) {
|
||||||
|
const [repoOwner, repo] = repoFullName.split('/');
|
||||||
|
object.repoOwner = repoOwner;
|
||||||
|
object.repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
@@ -1,9 +1,11 @@
|
|||||||
|
import { IJSONObject } from '@automatisch/types';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
stepId: string;
|
stepId: string;
|
||||||
key: string;
|
key: string;
|
||||||
|
parameters: IJSONObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async (_parent: unknown, params: Params, context: Context) => {
|
const getData = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
@@ -21,7 +23,7 @@ const getData = async (_parent: unknown, params: Params, context: Context) => {
|
|||||||
const appData = App.findOneByKey(step.appKey);
|
const appData = App.findOneByKey(step.appKey);
|
||||||
const AppClass = (await import(`../../apps/${step.appKey}`)).default;
|
const AppClass = (await import(`../../apps/${step.appKey}`)).default;
|
||||||
|
|
||||||
const appInstance = new AppClass(appData, connection.formattedData);
|
const appInstance = new AppClass(appData, connection.formattedData, params.parameters);
|
||||||
const command = appInstance.data[params.key];
|
const command = appInstance.data[params.key];
|
||||||
const fetchedData = await command.run();
|
const fetchedData = await command.run();
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ type Query {
|
|||||||
limit: Int!
|
limit: Int!
|
||||||
offset: Int!
|
offset: Int!
|
||||||
): ExecutionStepConnection
|
): ExecutionStepConnection
|
||||||
getData(stepId: String!, key: String!): JSONObject
|
getData(stepId: String!, key: String!, parameters: JSONObject): JSONObject
|
||||||
getCurrentUser: User
|
getCurrentUser: User
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ type ActionSubstepArgument {
|
|||||||
required: Boolean
|
required: Boolean
|
||||||
variables: Boolean
|
variables: Boolean
|
||||||
source: ActionSubstepArgumentSource
|
source: ActionSubstepArgumentSource
|
||||||
|
dependsOn: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionSubstepArgumentSource {
|
type ActionSubstepArgumentSource {
|
||||||
@@ -371,6 +372,7 @@ type TriggerSubstepArgument {
|
|||||||
required: Boolean
|
required: Boolean
|
||||||
variables: Boolean
|
variables: Boolean
|
||||||
source: TriggerSubstepArgumentSource
|
source: TriggerSubstepArgumentSource
|
||||||
|
dependsOn: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
type TriggerSubstepArgumentSource {
|
type TriggerSubstepArgumentSource {
|
||||||
|
2
packages/types/index.d.ts
vendored
2
packages/types/index.d.ts
vendored
@@ -85,6 +85,7 @@ export interface IFieldDropdown {
|
|||||||
clickToCopy: boolean;
|
clickToCopy: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
variables: boolean;
|
variables: boolean;
|
||||||
|
dependsOn: string[];
|
||||||
source: {
|
source: {
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -108,6 +109,7 @@ export interface IFieldText {
|
|||||||
clickToCopy: boolean;
|
clickToCopy: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
variables: boolean;
|
variables: boolean;
|
||||||
|
dependsOn: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type IField = IFieldDropdown | IFieldText;
|
type IField = IFieldDropdown | IFieldText;
|
||||||
|
@@ -15,14 +15,14 @@
|
|||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@types/jest": "^26.0.15",
|
"@types/jest": "^26.0.15",
|
||||||
"@types/lodash.template": "^4.5.0",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/luxon": "^2.0.8",
|
"@types/luxon": "^2.0.8",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.0.0",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"clipboard-copy": "^4.0.1",
|
"clipboard-copy": "^4.0.1",
|
||||||
"graphql": "^15.6.0",
|
"graphql": "^15.6.0",
|
||||||
"lodash.template": "^4.5.0",
|
"lodash": "^4.17.21",
|
||||||
"luxon": "^2.3.1",
|
"luxon": "^2.3.1",
|
||||||
"notistack": "^2.0.2",
|
"notistack": "^2.0.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
@@ -88,6 +88,7 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
|
|||||||
...step,
|
...step,
|
||||||
key: '',
|
key: '',
|
||||||
appKey,
|
appKey,
|
||||||
|
parameters: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ type Option = {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOption = (options: readonly Option[], value: string) => options.find(option => option.value === value);
|
const getOption = (options: readonly Option[], value: string) => options.find(option => option.value === value) || null;
|
||||||
|
|
||||||
function ControlledAutocomplete(props: ControlledAutocompleteProps): React.ReactElement {
|
function ControlledAutocomplete(props: ControlledAutocompleteProps): React.ReactElement {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
@@ -28,11 +28,10 @@ function ControlledAutocomplete(props: ControlledAutocompleteProps): React.React
|
|||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
description,
|
description,
|
||||||
|
options = [],
|
||||||
...autocompleteProps
|
...autocompleteProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (!autocompleteProps.options) return (<React.Fragment />);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
rules={{ required }}
|
rules={{ required }}
|
||||||
@@ -40,13 +39,14 @@ function ControlledAutocomplete(props: ControlledAutocompleteProps): React.React
|
|||||||
defaultValue={defaultValue || ''}
|
defaultValue={defaultValue || ''}
|
||||||
control={control}
|
control={control}
|
||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
render={({ field: { ref, onChange: controllerOnChange, onBlur: controllerOnBlur, ...field } }) => (
|
render={({ field: { ref, onChange: controllerOnChange, onBlur: controllerOnBlur, ...field }, fieldState }) => (
|
||||||
<div>
|
<div>
|
||||||
{/* encapsulated with an element such as div to vertical spacing delegated from parent */}
|
{/* encapsulated with an element such as div to vertical spacing delegated from parent */}
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...autocompleteProps}
|
{...autocompleteProps}
|
||||||
{...field}
|
{...field}
|
||||||
value={getOption(autocompleteProps.options, field.value)}
|
options={options}
|
||||||
|
value={getOption(options, field.value)}
|
||||||
onChange={(event, selectedOption, reason, details) => {
|
onChange={(event, selectedOption, reason, details) => {
|
||||||
const typedSelectedOption = selectedOption as Option;
|
const typedSelectedOption = selectedOption as Option;
|
||||||
if (typedSelectedOption?.value) {
|
if (typedSelectedOption?.value) {
|
||||||
@@ -63,8 +63,9 @@ function ControlledAutocomplete(props: ControlledAutocompleteProps): React.React
|
|||||||
|
|
||||||
<FormHelperText
|
<FormHelperText
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
error={Boolean(fieldState.isTouched && fieldState.error)}
|
||||||
>
|
>
|
||||||
{description}
|
{fieldState.isTouched ? fieldState.error?.message || description : description}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@@ -10,7 +10,10 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import ErrorIcon from '@mui/icons-material/Error';
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import type { IApp, IField, IStep } from '@automatisch/types';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
import type { BaseSchema } from 'yup';
|
||||||
|
import type { IApp, IField, IStep, ISubstep } from '@automatisch/types';
|
||||||
|
|
||||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||||
import TestSubstep from 'components/TestSubstep';
|
import TestSubstep from 'components/TestSubstep';
|
||||||
@@ -43,10 +46,57 @@ type FlowStepProps = {
|
|||||||
const validIcon = <CheckCircleIcon color="success" />;
|
const validIcon = <CheckCircleIcon color="success" />;
|
||||||
const errorIcon = <ErrorIcon color="error" />;
|
const errorIcon = <ErrorIcon color="error" />;
|
||||||
|
|
||||||
|
function generateValidationSchema(substeps: ISubstep[]) {
|
||||||
|
const fieldValidations = substeps?.reduce((allValidations, { arguments: args }) => {
|
||||||
|
if (!args || !Array.isArray(args)) return allValidations;
|
||||||
|
|
||||||
|
const substepArgumentValidations: Record<string, BaseSchema> = {};
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
const { key, required, dependsOn } = arg;
|
||||||
|
|
||||||
|
// base validation for the field if not exists
|
||||||
|
if (!substepArgumentValidations[key]) {
|
||||||
|
substepArgumentValidations[key] = yup.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof substepArgumentValidations[key] === 'object') {
|
||||||
|
// if the field is required, add the required validation
|
||||||
|
if (required) {
|
||||||
|
substepArgumentValidations[key] = substepArgumentValidations[key].required(`${key} is required.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the field depends on another field, add the dependsOn required validation
|
||||||
|
if (dependsOn?.length > 0) {
|
||||||
|
for (const dependsOnKey of dependsOn) {
|
||||||
|
// TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported.
|
||||||
|
// So the fields under the `parameters` key are subject to their siblings only and thus, `parameters.` is removed.
|
||||||
|
substepArgumentValidations[key] = substepArgumentValidations[key].when(`${dependsOnKey.replace('parameters.', '')}`, {
|
||||||
|
is: (value: string) => Boolean(value) === false,
|
||||||
|
then: (schema) => schema.required(`We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...allValidations,
|
||||||
|
...substepArgumentValidations,
|
||||||
|
}
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const validationSchema = yup.object({
|
||||||
|
parameters: yup.object(fieldValidations),
|
||||||
|
});
|
||||||
|
|
||||||
|
return yupResolver(validationSchema);
|
||||||
|
};
|
||||||
|
|
||||||
export default function FlowStep(
|
export default function FlowStep(
|
||||||
props: FlowStepProps
|
props: FlowStepProps
|
||||||
): React.ReactElement | null {
|
): React.ReactElement | null {
|
||||||
const { collapsed, index, onChange } = props;
|
const { collapsed, onChange } = props;
|
||||||
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
||||||
const step: IStep = props.step;
|
const step: IStep = props.step;
|
||||||
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
|
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
|
||||||
@@ -103,6 +153,8 @@ export default function FlowStep(
|
|||||||
handleChange({ step: val as IStep });
|
handleChange({ step: val as IStep });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stepValidationSchema = React.useMemo(() => generateValidationSchema(substeps), [substeps]);
|
||||||
|
|
||||||
if (!apps) return null;
|
if (!apps) return null;
|
||||||
|
|
||||||
const onContextMenuClose = (event: React.SyntheticEvent) => {
|
const onContextMenuClose = (event: React.SyntheticEvent) => {
|
||||||
@@ -171,7 +223,11 @@ export default function FlowStep(
|
|||||||
stepWithTestExecutionsData?.getStepWithTestExecutions as IStep[]
|
stepWithTestExecutionsData?.getStepWithTestExecutions as IStep[]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form defaultValues={step} onSubmit={handleSubmit}>
|
<Form
|
||||||
|
defaultValues={step}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
resolver={stepValidationSchema}
|
||||||
|
>
|
||||||
<ChooseAppAndEventSubstep
|
<ChooseAppAndEventSubstep
|
||||||
expanded={currentSubstep === 0}
|
expanded={currentSubstep === 0}
|
||||||
substep={{ name: 'Choose app & event', arguments: [] }}
|
substep={{ name: 'Choose app & event', arguments: [] }}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormProvider, useForm, FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
|
import { FormProvider, useForm, useWatch, FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
|
||||||
import type { UseFormProps } from 'react-hook-form';
|
import type { UseFormProps } from 'react-hook-form';
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
@@ -20,15 +20,26 @@ export default function Form(props: FormProps): React.ReactElement {
|
|||||||
defaultValues,
|
defaultValues,
|
||||||
resolver,
|
resolver,
|
||||||
render,
|
render,
|
||||||
mode,
|
mode = 'all',
|
||||||
...formProps
|
...formProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const methods: UseFormReturn = useForm({
|
const methods: UseFormReturn = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
|
reValidateMode: 'onBlur',
|
||||||
resolver,
|
resolver,
|
||||||
mode,
|
mode,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const form = useWatch({ control: methods.control, });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For fields having `dependsOn` fields, we need to re-validate the form.
|
||||||
|
*/
|
||||||
|
React.useEffect(() => {
|
||||||
|
methods.trigger();
|
||||||
|
}, [methods.trigger, form]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
methods.reset(defaultValues);
|
methods.reset(defaultValues);
|
||||||
}, [defaultValues]);
|
}, [defaultValues]);
|
||||||
|
@@ -3,6 +3,7 @@ import { useLazyQuery } from '@apollo/client';
|
|||||||
import MuiTextField from '@mui/material/TextField';
|
import MuiTextField from '@mui/material/TextField';
|
||||||
import type { IField, IFieldDropdown, IJSONObject } from '@automatisch/types';
|
import type { IField, IFieldDropdown, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import useDynamicData from 'hooks/useDynamicData';
|
||||||
import { GET_DATA } from 'graphql/queries/get-data';
|
import { GET_DATA } from 'graphql/queries/get-data';
|
||||||
import PowerInput from 'components/PowerInput';
|
import PowerInput from 'components/PowerInput';
|
||||||
import TextField from 'components/TextField';
|
import TextField from 'components/TextField';
|
||||||
@@ -26,7 +27,6 @@ type Option = {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeArguments = (args: IFieldDropdown["source"]["arguments"]): IJSONObject => args.reduce((result, { name, value }) => ({ ...result, [name as string]: value as string }), {});
|
|
||||||
const optionGenerator = (options: RawOption[]): Option[] => options?.map(({ name, value }) => ({ label: name as string, value: value as string }));
|
const optionGenerator = (options: RawOption[]): Option[] => options?.map(({ name, value }) => ({ label: name as string, value: value as string }));
|
||||||
const getOption = (options: Option[], value: string) => options?.find(option => option.value === value);
|
const getOption = (options: Option[], value: string) => options?.find(option => option.value === value);
|
||||||
|
|
||||||
@@ -51,24 +51,11 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
type,
|
type,
|
||||||
} = schema;
|
} = schema;
|
||||||
|
|
||||||
const [getData, { called, data }] = useLazyQuery(GET_DATA);
|
const { data, loading } = useDynamicData(stepId, schema);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (schema.type === 'dropdown' && stepId && schema.source && !called) {
|
|
||||||
getData({
|
|
||||||
variables: {
|
|
||||||
stepId,
|
|
||||||
...computeArguments(schema.source.arguments),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [getData, called, stepId, schema]);
|
|
||||||
|
|
||||||
|
|
||||||
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
||||||
|
|
||||||
if (type === 'dropdown') {
|
if (type === 'dropdown') {
|
||||||
const options = optionGenerator(data?.getData);
|
const options = optionGenerator(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
@@ -81,6 +68,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
value={getOption(options, value)}
|
value={getOption(options, value)}
|
||||||
onChange={console.log}
|
onChange={console.log}
|
||||||
description={description}
|
description={description}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -63,6 +63,7 @@ export const GET_APPS = gql`
|
|||||||
required
|
required
|
||||||
description
|
description
|
||||||
variables
|
variables
|
||||||
|
dependsOn
|
||||||
source {
|
source {
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
@@ -88,6 +89,7 @@ export const GET_APPS = gql`
|
|||||||
required
|
required
|
||||||
description
|
description
|
||||||
variables
|
variables
|
||||||
|
dependsOn
|
||||||
source {
|
source {
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const GET_DATA = gql`
|
export const GET_DATA = gql`
|
||||||
query GetData($stepId: String!, $key: String!) {
|
query GetData($stepId: String!, $key: String!, $parameters: JSONObject) {
|
||||||
getData(stepId: $stepId, key: $key)
|
getData(stepId: $stepId, key: $key, parameters: $parameters)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import template from 'lodash.template';
|
import template from 'lodash/template';
|
||||||
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
|
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
const interpolate = /{([\s\S]+?)}/g;
|
const interpolate = /{([\s\S]+?)}/g;
|
||||||
|
32
packages/web/src/helpers/computeVariables.ts
Normal file
32
packages/web/src/helpers/computeVariables.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import template from 'lodash/template';
|
||||||
|
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
const interpolate = /{([\s\S]+?)}/g;
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type IVariable = Omit<IAuthenticationStepField, "properties"> & Partial<Pick<IAuthenticationStepField, "properties">>;
|
||||||
|
|
||||||
|
const computeAuthStepVariables = (variableSchema: IVariable[], aggregatedData: IJSONObject): IJSONObject => {
|
||||||
|
const variables: Variables = {};
|
||||||
|
|
||||||
|
for (const variable of variableSchema) {
|
||||||
|
if (variable.properties) {
|
||||||
|
variables[variable.name] = computeAuthStepVariables(variable.properties, aggregatedData);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variable.value) {
|
||||||
|
const computedVariable = template(variable.value, { interpolate })(aggregatedData);
|
||||||
|
|
||||||
|
variables[variable.name] = computedVariable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default computeAuthStepVariables;
|
101
packages/web/src/hooks/useDynamicData.ts
Normal file
101
packages/web/src/hooks/useDynamicData.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useLazyQuery } from '@apollo/client';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import set from 'lodash/set';
|
||||||
|
import type { UseFormReturn } from 'react-hook-form';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import type { IField, IFieldDropdown, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { GET_DATA } from 'graphql/queries/get-data';
|
||||||
|
|
||||||
|
const variableRegExp = /({.*?})/g;
|
||||||
|
|
||||||
|
function computeArguments(args: IFieldDropdown["source"]["arguments"], getValues: UseFormReturn["getValues"]): IJSONObject {
|
||||||
|
const initialValue = {};
|
||||||
|
return args.reduce(
|
||||||
|
(result, { name, value }) => {
|
||||||
|
const isVariable = variableRegExp.test(value);
|
||||||
|
|
||||||
|
if (isVariable) {
|
||||||
|
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
||||||
|
const computedValue = getValues(sanitizedFieldPath);
|
||||||
|
|
||||||
|
if (!computedValue) throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
||||||
|
|
||||||
|
set(result, name, computedValue);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
set(result, name, value);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
initialValue
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the dynamic data for the given step.
|
||||||
|
* This hook must be within a react-hook-form context.
|
||||||
|
*
|
||||||
|
* @param stepId - the id of the step
|
||||||
|
* @param schema - the field that needs the dynamic data
|
||||||
|
*/
|
||||||
|
function useDynamicData(stepId: string | undefined, schema: IField) {
|
||||||
|
const lastComputedVariables = React.useRef({});
|
||||||
|
const [getData, { called, data, loading }] = useLazyQuery(GET_DATA);
|
||||||
|
const { getValues } = useFormContext();
|
||||||
|
const formValues = getValues();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return `null` when even a field is missing value.
|
||||||
|
*
|
||||||
|
* This must return the same reference if no computed variable is changed.
|
||||||
|
* Otherwise, it causes redundant network request!
|
||||||
|
*/
|
||||||
|
const computedVariables = React.useMemo(() => {
|
||||||
|
if (schema.type === 'dropdown' && schema.source) {
|
||||||
|
try {
|
||||||
|
const variables = computeArguments(schema.source.arguments, getValues);
|
||||||
|
|
||||||
|
// if computed variables are the same, return the last computed variables.
|
||||||
|
if (isEqual(variables, lastComputedVariables.current)) {
|
||||||
|
return lastComputedVariables.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastComputedVariables.current = variables;
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
/**
|
||||||
|
* `formValues` is to trigger recomputation when form is updated.
|
||||||
|
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
||||||
|
*/
|
||||||
|
}, [schema, formValues, getValues]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (schema.type === 'dropdown' && stepId && schema.source && computedVariables) {
|
||||||
|
getData({
|
||||||
|
variables: {
|
||||||
|
stepId,
|
||||||
|
...computedVariables,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [getData, stepId, schema, computedVariables]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
called,
|
||||||
|
data: data?.getData,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useDynamicData;
|
@@ -5,7 +5,7 @@ import Grid from '@mui/material/Grid';
|
|||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import * as yup from "yup";
|
import * as yup from 'yup';
|
||||||
|
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
|
12
yarn.lock
12
yarn.lock
@@ -4588,13 +4588,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/lodash" "*"
|
"@types/lodash" "*"
|
||||||
|
|
||||||
"@types/lodash.template@^4.5.0":
|
|
||||||
version "4.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.template/-/lodash.template-4.5.0.tgz#277654af717ed37ce2687c69f8f221c550276b7a"
|
|
||||||
integrity sha512-4LgHxK16IPbGR7TmXpPvNT7iNGsLCdQY6Rc0mi1a/JECt8et/D4hx6NMVAJej/d932sj1mJsg0QYHKL189O0Qw==
|
|
||||||
dependencies:
|
|
||||||
"@types/lodash" "*"
|
|
||||||
|
|
||||||
"@types/lodash@*", "@types/lodash@^4.14.149":
|
"@types/lodash@*", "@types/lodash@^4.14.149":
|
||||||
version "4.14.178"
|
version "4.14.178"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
||||||
@@ -4605,6 +4598,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d"
|
||||||
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==
|
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.182":
|
||||||
|
version "4.14.182"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||||
|
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||||
|
|
||||||
"@types/lru-cache@^5.1.0":
|
"@types/lru-cache@^5.1.0":
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef"
|
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef"
|
||||||
|
Reference in New Issue
Block a user