diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..220c9ed --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,24 @@ +name: Build Project & Commit +on: + pull_request: + types: + - closed +jobs: + build: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build Project + uses: actions/setup-node@v3 + with: + node-version: '12.x' + - run: npm install + - run: npm run build --if-present + - name: Commit Changes + uses: EndBug/add-and-commit@v9 + with: + add: './dist -f' + default_author: github_actor + message: 'HubSpot Action: Build Project' + pathspec_error_handling: exitImmediately diff --git a/README.md b/README.md index 683ac76..32e44ae 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# getting-started-project-template \ No newline at end of file +# getting-started-project-template + +This is the Getting Started project for HubSpot developer projects. This repo contains code that is intended to help developers to get up and running with developer projects quickly and easily. + +## Requirements +There are a few things that must be set up before you can make use of this getting started project. +- You must have an active HubSpot account. +- You must have the [HubSpot CLI](https://www.npmjs.com/package/@hubspot/cli) installed and set up. +- You must have access to developer projects (developer projects are not currently available to the public). + +## Usage +The HubSpot CLI is configured to pull from the latest release of this project. To get started, run the following CLI command in your terminal: + +`hs project create` + +The CLI should walk you throug the rest of the setup flow. diff --git a/hsproject.json b/hsproject.json index a18c016..e2567b0 100644 --- a/hsproject.json +++ b/hsproject.json @@ -1,4 +1,5 @@ { "name": "project_name", - "srcDir": "src" + "srcDir": "dist", + "preUploadScript": "npm run build" } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..86b4cd3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,24 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/node": { + "version": "18.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.1.tgz", + "integrity": "sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==", + "dev": true + }, + "rsync": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/rsync/-/rsync-0.6.1.tgz", + "integrity": "sha512-39HcwWuM67CQ9tHloazShXWUOWa2m3SGqX6XQhQMSj0VCQMkSI9PodoxM7/+hKf2p4v2umbhfoarYqd1gwII/w==", + "dev": true + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ea60bb8 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "devDependencies": { + "@types/node": "^18.6.5", + "rsync": "^0.6.1", + "typescript": "^4.7.4" + }, + "scripts": { + "copy-files": "rsync -av --exclude='*.ts' ./project/ dist", + "ts-compile": "tsc", + "build": "npm run ts-compile && npm run copy-files" + } +} diff --git a/src/app/app.functions/README.md b/project/app/app.functions/README.md similarity index 100% rename from src/app/app.functions/README.md rename to project/app/app.functions/README.md diff --git a/project/app/app.functions/crm-card.ts b/project/app/app.functions/crm-card.ts new file mode 100644 index 0000000..9906bb9 --- /dev/null +++ b/project/app/app.functions/crm-card.ts @@ -0,0 +1,63 @@ +// For external API calls +const axios = require('axios'); + +exports.main = async ( + context: Context, + sendResponse: (a: FunctionResponse) => void +) => { + + // Store contact firstname, configured as propertiesToSend in crm-card.json + const { firstname } = context.propertiesToSend; + + const introMessage = { + type: "text", + format: "markdown", + text: "_An example of a CRM card extension that displays data from Hubspot, uses ZenQuotes public API to display daily quote, and demonstrates custom actions using serverless functions._", + }; + + try { + const { data } = await axios.get("https://zenquotes.io/api/random"); + + const quoteSections = [ + { + type: "tile", + body: [ + { + type: "text", + format: "markdown", + text: `**Hello ${firstname}, here's your quote for the day**!` + }, + { + type: "text", + format: "markdown", + text: `**Quote**: ${data[0].q}` + }, + { + type: "text", + format: "markdown", + text: `**Author**: ${data[0].a}` + } + ] + }, + { + type: "button", + text: "Get new quote", + onClick: { + type: "SERVERLESS_ACTION_HOOK", + serverlessFunction: "crm-card" + } + } + ]; + + sendResponse({ sections: [introMessage, ...quoteSections] }); + } catch (error) { + // "message" will create an error feedback banner when it catches an error + sendResponse({ + message: { + type: 'ERROR', + body: `Error: ${error.message}` + }, + sections: [introMessage] + }); + } +}; diff --git a/src/app/app.functions/package.json b/project/app/app.functions/package.json similarity index 100% rename from src/app/app.functions/package.json rename to project/app/app.functions/package.json diff --git a/src/app/app.functions/serverless.json b/project/app/app.functions/serverless.json similarity index 100% rename from src/app/app.functions/serverless.json rename to project/app/app.functions/serverless.json diff --git a/src/app/app.json b/project/app/app.json similarity index 52% rename from src/app/app.json rename to project/app/app.json index d168ef8..978e765 100644 --- a/src/app/app.json +++ b/project/app/app.json @@ -1,6 +1,6 @@ { - "name": "App", - "description": "", + "name": "Example App", + "description": "An example private app that contains a single CRM card extension.", "scopes": [ "crm.objects.contacts.read", "crm.objects.contacts.write" @@ -10,7 +10,8 @@ "crm": { "cards": [ { - "file": "./crm-card.json" + "file": "./crm-card.json", + "version": "2" } ] } diff --git a/project/app/crm-card.json b/project/app/crm-card.json new file mode 100644 index 0000000..ec82f0b --- /dev/null +++ b/project/app/crm-card.json @@ -0,0 +1,17 @@ +{ + "type": "crm-card", + "version": "2", + "data": { + "title": "Example CRM Card", + "fetch": { + "targetFunction": "crm-card", + "objectTypes": [ + { + "name": "contacts", + "propertiesToSend": ["firstname"], + "actions":[] + } + ] + } + } +} diff --git a/project/types/main.d.ts b/project/types/main.d.ts new file mode 100644 index 0000000..0959367 --- /dev/null +++ b/project/types/main.d.ts @@ -0,0 +1,14 @@ +// Context types +interface PropertiesToSend { + firstname: String; +} + +interface Context { + propertiesToSend: PropertiesToSend; +} + +// Function Response types +interface FunctionResponse { + sections?: Array; + message?: { type: string; body: string }; +} diff --git a/src/app/app.functions/crm-card.js b/src/app/app.functions/crm-card.js deleted file mode 100644 index d9828dd..0000000 --- a/src/app/app.functions/crm-card.js +++ /dev/null @@ -1,74 +0,0 @@ -// For external API calls -const axios = require('axios'); -// For HubSpot API calls (HubSpot node API client) -const hubspot = require('@hubspot/api-client'); - -exports.main = async (context = {}, sendResponse) => { - // Instantiating HubSpot node API client - const hubspotClient = new hubspot.Client({ - accessToken: context.secrets.PRIVATE_APP_ACCESS_TOKEN, - }); - - // Static CRM card example - const staticCard = { - objectId: 1, - title: 'Sample project custom CRM card', - desc: `A custom CRM card is a UI extension that displays custom info from either HubSpot or an external system. It can also include custom actions—click the "Get Inspired" button to see example data from the ZenQuotes API.`, - }; - - // Make a call to zenquote public api and create a CRM card with result - const getExternalCard = async () => { - try { - const { data } = await axios.get('https://zenquotes.io/api/random'); - - return { - objectId: 2, - link: 'https://zenquotes.io/', - title: 'Inspirational quotes provided by ZenQuotes API', - quote: data[0].q, - author: data[0].a, - }; - } catch (error) { - throw new Error( - `There was an error fetching the quote': ${error.message}` - ); - } - }; - - // Make a call to the HubSpot contacts API and create a CRM card with result - const getInternalCard = async () => { - try { - // Fetching contacts from CRM - const resp = await hubspotClient.crm.contacts.basicApi.getPage(); - - return { - objectId: 3, - title: 'First contact in account', - firstname: resp.results[0].properties.firstname, - }; - } catch (error) { - throw new Error( - `There was an error fetching the contact': ${error.message}` - ); - } - }; - - // Assemble cards - try { - const internalCard = await getInternalCard(); - const externalCard = await getExternalCard(); - - sendResponse({ - results: [staticCard, internalCard, externalCard], - primaryAction: { - type: 'SERVERLESS_ACTION_HOOK', - serverlessFunction: 'crm-card', - label: 'Get Inspired', - }, - }); - } catch (error) { - throw new Error( - `There was an error creating these cards': ${error.message}` - ); - } -}; diff --git a/src/app/crm-card.json b/src/app/crm-card.json deleted file mode 100644 index 528607a..0000000 --- a/src/app/crm-card.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "type": "crm-card", - "data": { - "title": "App CRM Card", - "fetch": { - "targetFunction": "crm-card", - "objectTypes": [ - { - "name": "contacts", - "propertiesToSend": [], - "actions":[] - } - ] - }, - "display": { - "properties": [ - { - "name": "quote", - "label": "Quote", - "dataType": "STRING", - "options": [] - }, - { - "name": "desc", - "label": "What is this?", - "dataType": "STRING", - "options": [] - }, - { - "name": "author", - "label": "Author", - "dataType": "STRING", - "options": [] - }, - { "name": "firstname", - "label": "First name", - "dataType": "STRING", - "options":[] - } - ] - }, - "actions": { - "baseUrls": [] - } - } -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2d2dd4a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "outDir": "dist", + "rootDir": "./project" + } +}