feat: add new issue trigger in GitHub

This commit is contained in:
Ali BARIN
2022-05-07 23:04:29 +02:00
parent 54152a3564
commit 3a63fc376d
9 changed files with 230 additions and 8 deletions

View File

@@ -1,13 +1,16 @@
import { IJSONObject } from '@automatisch/types';
import ListRepos from './data/list-repos';
import ListBranches from './data/list-branches';
import ListLabels from './data/list-labels';
export default class Data {
listRepos: ListRepos;
listBranches: ListBranches;
listLabels: ListLabels;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.listRepos = new ListRepos(connectionData);
this.listBranches = new ListBranches(connectionData, parameters);
this.listLabels = new ListLabels(connectionData, parameters);
}
}

View File

@@ -0,0 +1,36 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListLabels {
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 labels = await this.client.paginate(this.client.rest.issues.listLabelsForRepo, this.options);
return labels.map((label) => ({
value: label.name,
name: label.name,
}));
}
}

View File

@@ -649,6 +649,98 @@
"name": "Test trigger"
}
]
},
{
"name": "New issue",
"key": "newIssue",
"description": "Triggers when a new issue is created",
"substeps": [
{
"key": "chooseAccount",
"name": "Choose account"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Which types of issues should this trigger on?",
"key": "issueType",
"type": "dropdown",
"description": "Defaults to any issue you can see.",
"required": true,
"variables": false,
"value": "all",
"options": [
{
"label": "Any issue you can see",
"value": "all"
},
{
"label": "Only issues assigned to you",
"value": "assigned"
},
{
"label": "Only issues created by you",
"value": "created"
},
{
"label": "Only issues you're mentioned in",
"value": "mentioned"
},
{
"label": "Only issues you're subscribed to",
"value": "subscribed"
}
]
},
{
"label": "Label",
"key": "label",
"type": "dropdown",
"description": "Only trigger on issues when this label is added.",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listLabels"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -11,6 +11,7 @@ import NewCommitComment from './triggers/new-commit-comment';
import NewLabel from './triggers/new-label';
import NewCollaborator from './triggers/new-collaborator';
import NewRelease from './triggers/new-release';
import NewIssue from './triggers/new-issue';
export default class Triggers {
newRepository: NewRepository;
@@ -25,6 +26,7 @@ export default class Triggers {
newLabel: NewLabel;
newCollaborator: NewCollaborator;
newRelease: NewRelease;
newIssue: NewIssue;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newRepository = new NewRepository(connectionData);
@@ -39,5 +41,6 @@ export default class Triggers {
this.newLabel = new NewLabel(connectionData, parameters);
this.newCollaborator = new NewCollaborator(connectionData, parameters);
this.newRelease = new NewRelease(connectionData, parameters);
this.newIssue = new NewIssue(connectionData, parameters);
}
}

View File

@@ -0,0 +1,88 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewIssue {
client?: Octokit;
connectionData?: IJSONObject;
repoOwner?: string;
repo?: string;
hasRepo?: boolean;
label?: string;
issueType?: 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 {
labels: this.label,
}
}
async listRepoIssues(options = {}, paginate = false) {
const listRepoIssues = this.client.rest.issues.listForRepo;
const extendedOptions = {
...this.options,
repo: this.repo,
owner: this.repoOwner,
filter: this.issueType,
...options,
};
if (paginate) {
return await this.client.paginate(listRepoIssues, extendedOptions);
}
return (await listRepoIssues(extendedOptions)).data;
}
async listIssues(options = {}, paginate = false) {
const listIssues = this.client.rest.issues.listForAuthenticatedUser;
const extendedOptions = {
...this.options,
...options,
};
if (paginate) {
return await this.client.paginate(listIssues, extendedOptions);
}
return (await listIssues(extendedOptions)).data;
}
async run(startTime: Date) {
const options = {
since: DateTime.fromJSDate(startTime).toISO(),
};
if (this.hasRepo) {
return await this.listRepoIssues(options, true);
}
return await this.listIssues(options, true);
}
async testRun() {
const options = {
per_page: 1,
};
if (this.hasRepo) {
return await this.listRepoIssues(options, false);
}
return await this.listIssues(options, false);
}
}

View File

@@ -9,6 +9,7 @@ export default class NewNotification {
connectionData?: IJSONObject;
repoOwner?: string;
repo?: string;
hasRepo?: boolean;
baseOptions = {
all: true,
participating: false,
@@ -24,10 +25,6 @@ export default class NewNotification {
assignOwnerAndRepo(this, parameters?.repo as string);
}
get hasRepo() {
return this.repoOwner && this.repo;
}
async listRepoNotifications(options = {}, paginate = false) {
const listRepoNotifications = this.client.rest.activity.listRepoNotificationsForAuthenticatedUser;

View File

@@ -1,8 +1,9 @@
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; }>(object: T, repoFullName: string): T {
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; hasRepo?: boolean; }>(object: T, repoFullName: string): T {
if (object && repoFullName) {
const [repoOwner, repo] = repoFullName.split('/');
object.repoOwner = repoOwner;
object.repo = repo;
object.hasRepo = true;
}
return object;

View File

@@ -11,7 +11,7 @@ interface ControlledAutocompleteProps extends AutocompleteProps<IFieldDropdownOp
description?: string;
}
const getOption = (options: readonly IFieldDropdownOption[], value: string) => options.find(option => option.value === value);
const getOption = (options: readonly IFieldDropdownOption[], value: string) => options.find(option => option.value === value) || null;
function ControlledAutocomplete(props: ControlledAutocompleteProps): React.ReactElement {
const { control } = useFormContext();
@@ -45,7 +45,7 @@ function ControlledAutocomplete(props: ControlledAutocompleteProps): React.React
value={getOption(options, field.value)}
onChange={(event, selectedOption, reason, details) => {
const typedSelectedOption = selectedOption as IFieldDropdownOption;
if (Object.prototype.hasOwnProperty.call(typedSelectedOption, 'value')) {
if (typedSelectedOption !== null && Object.prototype.hasOwnProperty.call(typedSelectedOption, 'value')) {
controllerOnChange(typedSelectedOption.value);
} else {
controllerOnChange(typedSelectedOption);

View File

@@ -69,11 +69,13 @@ function generateValidationSchema(substeps: ISubstep[]) {
// if the field depends on another field, add the dependsOn required validation
if (dependsOn?.length > 0) {
for (const dependsOnKey of dependsOn) {
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;
// 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.`),
then: (schema) => schema.notOneOf([''], missingDependencyValueMessage).required(missingDependencyValueMessage),
});
}
}