Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Components - grain #14685

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions components/grain/actions/get-recording/get-recording.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
INTELLIGENCE_NOTES_FORMAT_OPTIONS,
TRANSCRIPT_FORMAT_OPTIONS,
} from "../../common/constants.mjs";
import { parseObject } from "../../common/utils.mjs";
import grain from "../../grain.app.mjs";

export default {
key: "grain-get-recording",
name: "Get Recording",
description: "Fetches a specific recording by its ID from Grain, optionally including the transcript and intelligence notes. [See the documentation](https://grainhq.notion.site/grain-public-api-877184aa82b54c77a875083c1b560de9)",
version: "0.0.1",
type: "action",
props: {
grain,
recordId: {
propDefinition: [
grain,
"recordId",
],
},
transcriptFormat: {
type: "string",
label: "Transcript Format",
description: "Format for the transcript",
options: TRANSCRIPT_FORMAT_OPTIONS,
optional: true,
},
intelligenceNotesFormat: {
type: "string",
label: "Intelligence Notes Format",
description: "Format for the intelligence notes",
options: INTELLIGENCE_NOTES_FORMAT_OPTIONS,
optional: true,
},
allowedIntelligenceNotes: {
type: "string[]",
label: "Allowed Intelligence Notes",
description: "Whitelist of intelligence notes section titles",
optional: true,
},
},
async run({ $ }) {
const response = await this.grain.fetchRecording({
recordId: this.recordId,
params: {
transcript_format: this.transcriptFormat,
intelligence_notes_format: this.intelligenceNotesFormat,
allowed_intelligence_notes: parseObject(this.allowedIntelligenceNotes),
},
});

$.export("$summary", `Successfully fetched recording with ID ${this.recordId}`);
return response;
},
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
};
25 changes: 25 additions & 0 deletions components/grain/common/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const TRANSCRIPT_FORMAT_OPTIONS = [
{
label: "JSON",
value: "json",
},
{
label: "VTT",
value: "vtt",
},
];

export const INTELLIGENCE_NOTES_FORMAT_OPTIONS = [
{
label: "JSON",
value: "json",
},
{
label: "Markdown",
value: "md",
},
{
label: "Text",
value: "text",
},
];
24 changes: 24 additions & 0 deletions components/grain/common/utils.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const parseObject = (obj) => {
if (!obj) return undefined;

if (Array.isArray(obj)) {
return obj.map((item) => {
if (typeof item === "string") {
try {
return JSON.parse(item);
} catch (e) {
return item;
}
}
return item;
});
}
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
if (typeof obj === "string") {
try {
return JSON.parse(obj);
} catch (e) {
return obj;
}
}
return obj;
};
112 changes: 107 additions & 5 deletions components/grain/grain.app.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,113 @@
import { axios } from "@pipedream/platform";

export default {
type: "app",
app: "grain",
propDefinitions: {},
propDefinitions: {
recordId: {
type: "string",
label: "Record ID",
description: "The ID of the recording to fetch",
async options({ prevContext: { nextPage } }) {
const {
recordings, cursor,
} = await this.listRecordings({
params: {
cursor: nextPage,
},
});
return {
options: recordings.map(({
id: value, title: label,
}) => ({
value,
label,
})),
context: {
nextPage: cursor,
},
};
},
},
viewId: {
type: "string",
label: "View ID",
description: "The ID of the view to fetch",
async options({
type, prevContext: { nextPage },
}) {
const {
views, cursor,
} = await this.listViews({
params: {
type_filter: type,
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
cursor: nextPage,
},
});
return {
options: views.map(({
id: value, name: label,
}) => ({
value,
label,
})),
context: {
nextPage: cursor,
},
};
},
},
},
methods: {
// this.$auth contains connected account data
authKeys() {
console.log(Object.keys(this.$auth));
_baseUrl() {
return "https://grain.com/_/public-api";
},
_headers() {
return {
Authorization: `Bearer ${this.$auth.oauth_access_token}`,
};
},
_makeRequest({
$ = this, path, ...opts
}) {
return axios($, {
url: this._baseUrl() + path,
headers: this._headers(),
...opts,
});
},
Comment on lines +70 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling to API requests

The _makeRequest method should include error handling to provide meaningful error messages and handle API-specific error responses.

Consider adding error handling:

    _makeRequest({
      $ = this, path, ...opts
    }) {
-     return axios($, {
-       url: this._baseUrl() + path,
-       headers: this._headers(),
-       ...opts,
-     });
+     return axios($, {
+       url: this._baseUrl() + path,
+       headers: this._headers(),
+       ...opts,
+     }).catch((error) => {
+       const status = error.response?.status;
+       const message = error.response?.data?.message || error.message;
+       throw new Error(`Grain API request failed with ${status}: ${message}`);
+     });
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_makeRequest({
$ = this, path, ...opts
}) {
return axios($, {
url: this._baseUrl() + path,
headers: this._headers(),
...opts,
});
},
_makeRequest({
$ = this, path, ...opts
}) {
return axios($, {
url: this._baseUrl() + path,
headers: this._headers(),
...opts,
}).catch((error) => {
const status = error.response?.status;
const message = error.response?.data?.message || error.message;
throw new Error(`Grain API request failed with ${status}: ${message}`);
});
},

listRecordings(opts = {}) {
return this._makeRequest({
path: "/recordings",
...opts,
});
},
listViews(opts = {}) {
return this._makeRequest({
path: "/views",
...opts,
});
},
fetchRecording({
recordId, ...opts
}) {
return this._makeRequest({
path: `/recordings/${recordId}`,
...opts,
});
},
createWebhook(opts = {}) {
return this._makeRequest({
method: "POST",
path: "/hooks",
...opts,
});
},
deleteWebhook(hookId) {
return this._makeRequest({
method: "DELETE",
path: `/hooks/${hookId}`,
});
},
},
};
};
7 changes: 5 additions & 2 deletions components/grain/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/grain",
"version": "0.0.1",
"version": "0.1.0",
"description": "Pipedream Grain Components",
"main": "grain.app.mjs",
"keywords": [
Expand All @@ -11,5 +11,8 @@
"author": "Pipedream <[email protected]> (https://pipedream.com/)",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^3.0.3"
}
}
}
44 changes: 44 additions & 0 deletions components/grain/sources/common/base.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import grain from "../../grain.app.mjs";

export default {
props: {
grain,
http: "$.interface.http",
db: "$.service.db",
},
methods: {
_getHookId() {
return this.db.get("hookId");
},
_setHookId(hookId) {
this.db.set("hookId", hookId);
},
},
hooks: {
async activate() {
const response = await this.grain.createWebhook({
data: {
version: 2,
hook_url: this.http.endpoint,
view_id: this.viewId,
actions: this.getAction(),
},
});
this._setHookId(response.id);
},
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
async deactivate() {
const webhookId = this._getHookId();
await this.grain.deleteWebhook(webhookId);
},
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
},
async run({ body }) {
if (!body.data) return;

const ts = Date.parse(new Date());
this.$emit(body, {
id: `${body.data.id}-${ts}`,
summary: this.getSummary(body),
ts: ts,
});
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import common from "../common/base.mjs";
import sampleEmit from "./test-event.mjs";

export default {
...common,
key: "grain-new-highlight-instant",
name: "New Highlight (Instant)",
description: "Emit new event when a highlight that matches the filter is added.",
version: "0.0.1",
type: "source",
dedupe: "unique",
props: {
...common.props,
viewId: {
propDefinition: [
common.props.grain,
"viewId",
() => ({
type: "highlights",
}),
],
},
},
methods: {
...common.methods,
getAction() {
return [
"added",
];
},
getSummary({ data }) {
return `New highlight added: ${data.id}`;
},
},
sampleEmit,
};
17 changes: 17 additions & 0 deletions components/grain/sources/new-highlight-instant/test-event.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default {
"type": "highlight_added",
"user_id": "aea95745-99e9-4609-8623-c9efa2926b82",
"data": {
"id": "vjQRUKsWw0aFpCT3531eGbr8V0HJrMjKMEIcAUmP",
"recording_id": "b5185ccb-9a08-458c-9be1-db17a03fb14c",
"text": "testing 123 #test",
"transcript": "expected, that there was a mews in a lane which runs down by one wall of the garden. I lent the ostlers a hand in rubbing down their horses, and received in exchange twopence, a glass of half-and-half, two fills of shag tobacco, and as much information as I could desire about Miss Adler, to say nothing of half a dozen other people in",
"speakers": ["Andy Arbol"],
"timestamp": 3080,
"duration": 15000,
"created_datetime": "2021-07-29T23:16:34Z",
"url": "https://grain.com/highlight/vjQRUKsWw0aFpCT3531eGbr8V0HJrMjKMEIcAUmP",
"thumbnail_url": "https://media.grain.com/clips/v1/a14e5af9-d28e-43e9-902b-bc07419082eb/57zB8z52l7BKPoOvkS9KNyUi7LDSsNEh.jpeg",
"tags": ["test"]
michelle0927 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import common from "../common/base.mjs";
import sampleEmit from "./test-event.mjs";

export default {
...common,
key: "grain-new-recording-instant",
name: "New Recording (Instant)",
description: "Emit new event when a recording that matches the filter is added.",
version: "0.0.1",
type: "source",
dedupe: "unique",
props: {
...common.props,
viewId: {
propDefinition: [
common.props.grain,
"viewId",
() => ({
type: "recordings",
}),
],
},
},
methods: {
...common.methods,
getAction() {
return [
"added",
];
},
getSummary({ data }) {
return `New recording added: ${data.id}`;
},
},
sampleEmit,
};
12 changes: 12 additions & 0 deletions components/grain/sources/new-recording-instant/test-event.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default {
"type": "recording_added",
"user_id": "aea95745-99e9-4609-8623-c9efa2926b82",
"data": {
"id": "b5185ccb-9a08-458c-9be1-db17a03fb14c",
"title": "Sample Recording",
"url": "https://grain.com/recordings/b5185ccb-9a08-458c-9be1-db17a03fb14c/Kz5t1kAyPtt78hcxbSOJHJzFiPpZmUIeDVFXWzP0",
"start_datetime": "2021-07-29T23:13:17Z",
"end_datetime": "2021-07-29T23:16:18Z",
"public_thumbnail_url": null // Only non-null if recording share state is public
}
}
Loading