Aha! Develop | Importer extension contributions
Importer contributions give Aha! Develop users a way to bring records one at a time into Aha! Develop from another tool like GitHub, Jira, or Zendesk.
Importers give you visibility into another tool's backlog and create a copy of each record as you pull it into your own Aha! Develop account. From the Zendesk importer, for example, you can import tickets escalated to your development team and track work in Aha! Develop. From Jira, you can select a project and copy in individual stories while you are testing Aha! Develop with your team.
The intent of importer contributions is not to create an ongoing integration or to import an entire backlog at once. For that, you should use the CSV import.
Click any of the following links to skip ahead:
Current importers
We have created importers with the following tools. You can always create your own importer extension to a tool not listed here:
Asana importer | Import individual Asana tasks straight to your Aha! Develop backlog. | ||
Azure DevOps importer | Import individual work items from Azure DevOps straight to your Aha! Develop backlog. | ||
GitHub importer | Import individual GitHub issues straight to your Aha! Develop backlog. | ||
GitLab importer | Import individual GitLab issues straight to your Aha! Develop backlog. | ||
Google Sheets importer | Import rows from Google Sheet straight to your Aha! Develop backlog. | ||
Jira importer | Import individual Jira issues straight to your Aha! Develop backlog. | ||
Rally importer | Import individual Rally stories straight to your Aha! Develop backlog. | ||
Salesforce Service Cloud importer | Import Salesforce cases directly to your Aha! Develop backlog. | ||
Sentry importer | Create a feature in Aha! Develop directly from Sentry Issues. | ||
Trello importer | View Trello boards and import individual cards straight to your Aha! Develop backlog. | ||
Zendesk importer | Import individual Zendesk tickets straight to your Aha! Develop backlog. |
Administrators in your Aha! Develop account can install these importers by navigating to Plan Sprint planning or Work Board, then clicking Import from the Change view type dropdown in the upper left. Once an administrator has installed an importer extension in your Aha! Develop account, any user in your account can authenticate with the importer, and any user with owner or contributor user permissions can drag work into your Aha! Develop account.
Schema
Importer extensions provide a title and an entrypoint. The entrypoint registers callbacks (described in the API section below) to control the behavior of the import and wrap the development tool.
{
"ahaExtension": {
"contributes": {
"importers": {
"issues": {
"title": "GitHub",
"entryPoint": "src/import.js"
}
}
}
}
}
API
Importer contributions can register callbacks in order to fetch record data to import and customize the import process. All callbacks are optional, other than listCandidates
, which must be implemented in order to show importable records.
To register a callback for your importer contribution, first request a reference to it:
const importer = aha.getImporter("aha-develop.github-import.issues");
Once you have the reference, you can register the following callbacks:
listFilters
The listFilters
returns a set of filter definitions that will show up on the import screen to help a user of your extension specify which records to fetch.
importer.on({ action: "listFilters" }, ({}, {identifier, settings}) => {
return {
repo: {
title: "Repository",
required: true,
type: "text",
},
};
});
Filters must have a title and a type. You can also specify whether a filter is required in order to perform a search.
filterValues
Some filters will require information from the external server. For example, when filtering to an assigned user, you may want to fetch the list of users from the system you are importing from. filterValues
returns the list of possible values for a filter field.
importer.on({ action: "filterValues" }, async ({ filterName, filters }, {identifier, settings}) => {
let values = [];
switch (filterName) {
case "repo":
values = await autocompleteRepo(filters.repo);
}
return values;
});
filterValues
is given two parameters:
filterName
is the name of the filter whose values are being requested.filters
are the current values set to each of your extension's filters.
It should return an array of { text: "...", value: "..." }
objects. text
will be displayed and value
is the value assigned to the filter when it is selected. text
is optional — if it is unspecified, value
will be displayed instead.
listCandidates
listCandidates
returns a page of record data that can be imported into Aha! Develop:
importer.on({ action: "listCandidates" }, async ({ filters, nextPage }, {identifier, settings}) => {
return findIssues(filters.repo, nextPage);
});
It must return an object:
{ records: [...], nextPage: ... }
records
is a list of objects. Each record must have a uniqueId
field and a name
field but the rest of the information is up to you. Your record
will be passed into other callbacks so it can store data you need for those.
There are special fields identifier
and url
that are used for display if the renderRecord
callback is not registered.
nextPage
is an arbitrary identifier that you will use to fetch the next page. For some systems, it could be a page number. For other systems, it could be a cursor. Whatever you return in the nextPage
field will be passed back into this function when the next page is fetched. Set nextPage
to null
when there is no more data to return.
listCandidates
is given two parameters:
filters
is the current values set to each of your extension's filters.nextPage
is the value your extension returned asnextPage
from the last time this function was called. It will benull
whenever a completely new set of records are fetched.
renderRecord
renderRecord
can be used to customize how each record is displayed in the import views. If it is unspecified, you will get a simple default card using the name
, identifier
, and url
returned from listCandidates
.
// Render a single record.
importer.on({ action: "renderRecord" }, ({ record, onUnmounted }, { identifier, settings }) => {
onUnmounted(() => {
console.log("Un-mounting component for", record.identifier);
});
return `${record.identifier} ${record.name}`;
});
renderRecord
should return the HTML that will be displayed for the record. The return value can be one of three types:
A plain text string will be used as-is. HTML elements in the string will be escaped.
A single DOM node or an array of DOM nodes.
A React component.
renderRecord
takes two parameters:
record
is the data for the record to be rendered. It is the samerecord
as was returned fromlistCandidates
.onUnmounted
takes a function that will be called when the record will no longer appear in the user interface. It can be used to unregister event handlers or perform any other action that is necessary to clean up. For React components, it is not necessary to unmount the component. It will be handled automatically.
See view contributions for more examples of rendering.
importRecord
The importRecord
callback allows you to change a record after it has been imported. It is useful for updating the new record with extra information the extension fetched from the external system:
importer.on({ action: "importRecord" }, async ({ importRecord, ahaRecord }, {identifier, settings}) => {
ahaRecord.name = "[GitHub] " + ahaRecord.name;
await ahaRecord.save();
});
If you make a change, you are responsible for saving the record.
importRecord
takes two parameters:
importRecord
is therecord
data from thelistCandidates
callback.ahaRecord
is anApplicationModel
instance corresponding toimportRecord
.