Importing data from Canny to Cycle has never been easier! This guide will walk you through using the Cycle API to seamlessly transfer your valuable customer feedback from Canny into your Cycle inbox alongside your features from your Canny roadmap.
For more details and to access the script, check out the code repository. π
What this script does
Fetches each Canny post from all your boards.
Retrieves votes and comments for each post.
Creates a document with the desired type (from config) for each post in Cycle.
Links feedback to each vote for the created document.
Tags all imported documents with an imported property, allowing you to filter or bulk delete these documents if needed.
How to use the script
Configure Your Data:
Fill your data in ./config.ts.
Run the Import Script:
Execute the command npm run import:canny.
Code overview
Letβs try to understand how the script works and why itβs done that way:
Cycle core logic
From your Cycle workspace, you get a unique slug, the script will proceed as follows
Retrieve the βproductIdβ from the slug (βproductβ is the old name for βworkspaceβ)
Create the import attribute (βattributeβ is the tech name for βpropertiesβ) in order to let you filter on imported docs in views
Link the attributes to doc type in order to assign the attribute value to imported docs
Fetch Canny data and create docs out of it
Fetching Canny
The script defines a fetchCanny function to retrieve data from Canny's API. It handles pagination to ensure all posts, comments, and votes are fetched.
The script creates documents in Cycle for each post, along with linked feedback for each vote:
const createDocAndFeedback = async (workspaceId: string, post: Post, comments: Comment[], votes: Vote[], importAttributeData: any, docTypeToImport: any, insight: any) => {
const createdDoc = await createDoc({
workspaceId,
attributes: [importAttributeData],
contentJSON: getJSONDocContent({ post, comments: getFormattedComments(comments) }),
doctypeId: docTypeToImport.id,
title: post.title,
});
if (createdDoc) {
for (const vote of votes) {
const voteFeedback = await createFeedback({
workspaceId,
attributes: [importAttributeData],
content: `Vote for ${post.title}`,
sourceUrl: post.url,
title: `Vote from ${vote.voter?.name || 'unknown'} on ${post.title}`,
customerEmail: vote.voter.email,
});
await createDoc({
doctypeId: insight.id,
workspaceId,
attributes: [importAttributeData],
contentJSON: '',
title: `Vote from ${vote.voter?.name || 'unknown'} on ${post.title}`,
customerId: voteFeedback.customer.id,
docSourceId: voteFeedback.id,
parentId: createdDoc.id,
});
}
}
};
As you can see, the way to have feedback connected to features in Cycle is using Insight concept which is a certain type of doc plus a link between feedback and feature. The link between a feedback and an insight is made via a doc containing a βdocSourceIdβ. Then to link the insight to the feature, use a the βparentIdβ field.
So you always need to have the parent before the child in any relation import in Cycle.
The right order to proceed is then:
Create the feature (Well the name is up to you since itβs fully customisable)
Create the feedback
Create the link between both (insight)
Hope this will help you to build crazy integrations with our API.
For more details and to access the script, check out the code repository. π