Loading…

Star4.1kBook a meeting

GitHub Integration & Automations

The Operation plugin's GitHub App integration (issue ↔ task sync) and its automation triggers and actions.

Backend code lives under backend/plugins/operation_api/src/modules/githubIntegration/, src/modules/automations/, and src/utils/ (githubClient.ts, githubWebhookHandler.ts). The UI lives at settings/operation/github in operation_ui.

Data models

  • operation_github_configs: links one team to one repo: { teamId, repoName, installationId, syncMode: 'oneWay' | 'twoWay', subdomain }. Unique indexes on { teamId, subdomain } and { repoName, subdomain }.
  • operation_github_connections: an installed GitHub App org: { installationId, orgName, orgAvatarUrl, orgType, initiatedUserId, isActive, subdomain }. Unique on { installationId, subdomain }.
  • operation_github_milestone_mappings: maps erxes milestones to GitHub milestones per repo: { subdomain, installationId, repoName, erxesMilestoneId, githubMilestoneId, githubMilestoneNumber, githubMilestoneTitle }.

Tasks and triage items carry githubIssueNumber, githubIssueUrl, githubRepoName once linked. Triage enforces a unique sparse index on { githubRepoName, githubIssueNumber }.

Setup and authentication

The integration is a GitHub App authenticated with GITHUB_APP_ID + GITHUB_PRIVATE_KEY (installation tokens via getInstallationOctokit); webhooks are verified with GITHUB_WEBHOOK_SECRET (HMAC-SHA256 over the raw body, x-hub-signature-256).

  • GET /pl:operation/integrations/github/setup: returns a small HTML page that postMessage({ type: 'github-install-complete' }) to window.opener and closes the popup. Point the GitHub App's post-install redirect here.
  • POST /pl:operation/integrations/github/webhook: GitHub webhook receiver. Returns 400 without a resolvable subdomain, 401 on missing/invalid signature, 500 when GITHUB_WEBHOOK_SECRET is unset, pong on ping.

Webhook events handled:

x-github-event Behavior
installation created/unsuspend upserts an active GithubConnection; deleted/suspend deactivates it
issues opened Creates a triage item on the configured team and appends <!-- erxes-task-id: ... --> to the issue body
issues opened/reopened/closed For an already-linked task, moves it to the team's first status of type UNSTARTED / COMPLETED / CANCELLED (closed + not_planned or duplicate → cancelled)
issues milestoned/demilestoned Sets/clears the task's milestoneId via GithubMilestoneMapping; only when the config's teamId matches the task's team
anything else ignored

Bot senders (sender.type === 'Bot') are skipped to prevent sync loops.

Outbound (two-way) sync

When a team's config has syncMode: 'twoWay':

  • createTask opens a GitHub issue (POST /repos/{owner}/{repo}/issues) with a body linking back to https://<subdomain>.erxes.io/operation/team/<teamId>/tasks/<id> plus the erxes-task-id marker, then writes the issue number/URL onto the task and pushes the milestone when milestoneId is set.
  • updateTask pushes status (updateGithubIssueState maps COMPLETED → closed/completed, CANCELLED → closed/not_planned, anything else → open) and milestone changes (updateGithubIssueMilestone, which looks up or creates the GitHub milestone by title via resolveGithubMilestoneNumber).

GitHub failures are logged and swallowed; a task mutation never fails because the issue sync did. In oneWay mode only inbound (GitHub → erxes) events apply. The sync lives in the task mutations.

Queries and mutations

Operation Kind Permission
getGithubConfigByTeam(teamId) / getAllGithubConfigs(installationId) query teamRead
upsertGithubConfig(teamId, repoName, installationId, syncMode) mutation teamUpdate
operationGithubDisconnectTeam(teamId) mutation teamUpdate
getGithubConnection / getGithubConnections / getGithubRepositories(installationId) query teamRead
disconnectGithubConnection(installationId) mutation teamUpdate

upsertGithubConfig validates the team exists, the installation has an active connection, the repo isn't already linked to another team, repoName is owner/repo form, and the repo is reachable through that installation (GET /repos/{owner}/{repo}). Re-linking a team to a different installation/repo deletes its milestone mappings; operationGithubDisconnectTeam does the same; disconnectGithubConnection refuses while any team still references the installation.

Automations

meta/automations.ts connects modules/automations/ (handlers in automationHandlers.ts, actions in actions/) to the platform automation service.

Triggers (operationAutomationConstants.triggers):

Trigger Target type Notes
Task operation:task.tasks segment enrollment
Project operation:project.projects segment enrollment
Project completed operation:project.projects (relationType: 'completed') custom; fires when every task in the project is complete
Milestone reached operation:project.milestones (relationType: 'reached') custom; milestone tasks hit the configured completion mode
Team work completed operation:team.teams (relationType: 'completed') custom; team tasks hit the completion mode

The three custom triggers support completion modes every/some/first/last (checkCustomTrigger in automationHandlers.ts evaluates task status types under the selector).

Actions (actions/):

Action Module Notes
Create task task createTaskAction; target source operation:task.tasks
Create project project createProjectAction; target source operation:project.projects

setProperties supports writing fields on tasks, projects, milestones, and teams via setProperty rules and setPropertyTargets. Automation task outputs include a DOMAIN-based link (/operation/tasks/<id>).

Environment variables

Variable Purpose
GITHUB_APP_ID GitHub App id for installation tokens
GITHUB_PRIVATE_KEY App private key (PEM)
GITHUB_WEBHOOK_SECRET Webhook signature secret; webhook 500s without it
DOMAIN Task links embedded in GitHub issue bodies and automation outputs
VERSION saas changes org-scoped behavior (cycle worker, org flags)

Sync not working?

If opened issues do not create triage items, no GithubConfig exists for that installationId + repoName + subdomain. If status changes do not reach GitHub, outbound sync only applies to twoWay configs where githubRepoName still matches; outbound errors are logged, not thrown.

Operate on one system.

Talk to our team about consolidating your stack onto erxes. 30-minute walkthrough, custom to your org.