From dbdce48f5a96fd27607fe0a0b95f6da36eed7383 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Fri, 28 Aug 2020 18:50:24 +1000 Subject: [PATCH 01/12] email on trigger cloud function --- .../functions/src/emailOnTrigger/index.ts | 134 ++++++++++++++++++ cloud_functions/functions/src/index.ts | 1 + 2 files changed, 135 insertions(+) create mode 100644 cloud_functions/functions/src/emailOnTrigger/index.ts diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts new file mode 100644 index 00000000..d1c4f752 --- /dev/null +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -0,0 +1,134 @@ +import { firestore } from "firebase-functions"; + +import { sendEmail } from "../utils/email"; +import _config from "../functionConfig"; // generated using generateConfig.ts +const functionConfig: any = _config; +type EmailOnTriggerConfig = { + collectionPath: string; + templateId: string; + categories: string[]; + onCreate: Boolean; + from: Function; + to: Function; + requiredFields: string[]; + shouldSend: ( + snapshot: + | firestore.DocumentSnapshot + | { + before: firestore.DocumentSnapshot; + after: firestore.DocumentSnapshot; + } + ) => Boolean; + onUpdate: Boolean; +}; + +const hasRequiredField = (requiredFields: string[], snapshotData: any) => + requiredFields.reduce((acc: boolean, currField: string) => { + if ( + snapshotData[currField] === undefined || + snapshotData[currField] === null + ) + return false; + else return acc; + }, true); +const emailOnCreate = (config: EmailOnTriggerConfig) => + firestore + .document(`${config.collectionPath}/{docId}`) + .onCreate(async (snapshot) => { + try { + const snapshotData = snapshot.data(); + if (!snapshotData) throw Error("no snapshot data"); + + const shouldSend = config.shouldSend(snapshot); + const hasAllRequiredFields = hasRequiredField( + config.requiredFields, + snapshotData + ); + const to = await config.to(snapshotData); + const from = await config.from(snapshotData); + if (shouldSend && hasAllRequiredFields) { + const msg = { + from, + personalizations: [ + { + to, + dynamic_template_data: { + ...snapshotData, + }, + }, + ], + template_id: config.templateId, + categories: config.categories, + attachments: snapshotData.attachments, + }; + const resp = await sendEmail(msg); + console.log(JSON.stringify(resp)); + return true; + } else { + console.log("requirements were not met"); + return false; + } + } catch (error) { + console.warn(JSON.stringify(error.response.body)); + return false; + } + }); + +const emailOnUpdate = (config: EmailOnTriggerConfig) => + firestore + .document(`${config.collectionPath}/{docId}`) + .onUpdate(async (change) => { + try { + const beforeData = change.before.data(); + const afterData = change.after.data(); + if (!beforeData || !afterData) throw Error("no data found in snapshot"); + const shouldSend = config.shouldSend(change); + const hasAllRequiredFields = hasRequiredField( + config.requiredFields, + afterData + ); + const dynamic_template_data = config.requiredFields.reduce( + (acc: any, curr: string) => { + return { ...acc, [curr]: afterData[curr] }; + }, + {} + ); + if (shouldSend && hasAllRequiredFields) { + const from = await config.from(afterData); + const to = await config.to(afterData); + const msg = { + from, + personalizations: [ + { + to, + dynamic_template_data, + }, + ], + template_id: config.templateId, + categories: config.categories, + }; + const resp = await sendEmail(msg); + console.log(JSON.stringify(resp)); + + return true; + } else { + console.log("requirements were not met"); + return false; + } + } catch (error) { + console.warn(error); + return false; + } + }); + +const emailOnTriggerFns = (config: EmailOnTriggerConfig) => + Object.entries({ + onCreate: config.onCreate ? emailOnCreate(config) : null, + onUpdate: config.onUpdate ? emailOnUpdate(config) : null, + }).reduce((a, [k, v]) => (v === null ? a : { ...a, [k]: v }), {}); + +export const FT_email = { + [functionConfig.collectionPath + .replace(/\//g, "_") + .replace(/_{.*?}_/g, "_")]: emailOnTriggerFns(functionConfig), +}; diff --git a/cloud_functions/functions/src/index.ts b/cloud_functions/functions/src/index.ts index 352adeeb..bdba22ef 100644 --- a/cloud_functions/functions/src/index.ts +++ b/cloud_functions/functions/src/index.ts @@ -17,3 +17,4 @@ export { FT_subTableStats } from "./subTableStats"; export { FT_compressedThumbnail } from "./compressedThumbnail"; export { actionScript } from "./actionScript"; export { webhook } from "./webhooks"; +export { FT_email } from "./emailOnTrigger"; From b62aeffeeb21a1dcb4c11e53318c2c1f0e94d155 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Fri, 28 Aug 2020 19:37:50 +1000 Subject: [PATCH 02/12] refactor send email cloud function --- .../functions/src/buildTriggers/index.ts | 2 +- .../functions/src/emailOnTrigger/index.ts | 19 +++++-------------- cloud_functions/functions/src/utils/index.ts | 6 ++++++ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cloud_functions/functions/src/buildTriggers/index.ts b/cloud_functions/functions/src/buildTriggers/index.ts index 30bd2eae..4a808510 100644 --- a/cloud_functions/functions/src/buildTriggers/index.ts +++ b/cloud_functions/functions/src/buildTriggers/index.ts @@ -87,7 +87,7 @@ export const cloudBuildUpdates = functions.pubsub if (query.docs.length !== 0) { const update = { status }; - if (status === "SUCCESS") { + if (status === "SUCCESS" || status === "FAILURE") { update["buildDuration.end"] = serverTimestamp(); } await query.docs[0].ref.update(update); diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts index d1c4f752..f51b5f16 100644 --- a/cloud_functions/functions/src/emailOnTrigger/index.ts +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -1,6 +1,7 @@ import { firestore } from "firebase-functions"; import { sendEmail } from "../utils/email"; +import { hasRequiredFields } from "../utils"; import _config from "../functionConfig"; // generated using generateConfig.ts const functionConfig: any = _config; type EmailOnTriggerConfig = { @@ -21,16 +22,6 @@ type EmailOnTriggerConfig = { ) => Boolean; onUpdate: Boolean; }; - -const hasRequiredField = (requiredFields: string[], snapshotData: any) => - requiredFields.reduce((acc: boolean, currField: string) => { - if ( - snapshotData[currField] === undefined || - snapshotData[currField] === null - ) - return false; - else return acc; - }, true); const emailOnCreate = (config: EmailOnTriggerConfig) => firestore .document(`${config.collectionPath}/{docId}`) @@ -40,7 +31,7 @@ const emailOnCreate = (config: EmailOnTriggerConfig) => if (!snapshotData) throw Error("no snapshot data"); const shouldSend = config.shouldSend(snapshot); - const hasAllRequiredFields = hasRequiredField( + const hasAllRequiredFields = hasRequiredFields( config.requiredFields, snapshotData ); @@ -83,7 +74,7 @@ const emailOnUpdate = (config: EmailOnTriggerConfig) => const afterData = change.after.data(); if (!beforeData || !afterData) throw Error("no data found in snapshot"); const shouldSend = config.shouldSend(change); - const hasAllRequiredFields = hasRequiredField( + const hasAllRequiredFields = hasRequiredFields( config.requiredFields, afterData ); @@ -128,7 +119,7 @@ const emailOnTriggerFns = (config: EmailOnTriggerConfig) => }).reduce((a, [k, v]) => (v === null ? a : { ...a, [k]: v }), {}); export const FT_email = { - [functionConfig.collectionPath + [`${functionConfig.collectionPath .replace(/\//g, "_") - .replace(/_{.*?}_/g, "_")]: emailOnTriggerFns(functionConfig), + .replace(/_{.*?}_/g, "_")}`]: emailOnTriggerFns(functionConfig), }; diff --git a/cloud_functions/functions/src/utils/index.ts b/cloud_functions/functions/src/utils/index.ts index fada78ec..1fd8c200 100644 --- a/cloud_functions/functions/src/utils/index.ts +++ b/cloud_functions/functions/src/utils/index.ts @@ -9,3 +9,9 @@ export const replacer = (data: any) => (m: string, key: string) => { const defaultValue = key.split(":")[1] || ""; return _.get(data, objKey, defaultValue); }; + +export const hasRequiredFields = (requiredFields: string[], data: any) => + requiredFields.reduce((acc: boolean, currField: string) => { + if (data[currField] === undefined || data[currField] === null) return false; + else return acc; + }, true); From 325efb9480709e4c8a1f7cfe9b65822782be9993 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Sat, 29 Aug 2020 00:48:37 +1000 Subject: [PATCH 03/12] wrap FT_email collectionPath --- cloud_functions/functions/src/emailOnTrigger/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts index f51b5f16..ec2222cd 100644 --- a/cloud_functions/functions/src/emailOnTrigger/index.ts +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -119,7 +119,7 @@ const emailOnTriggerFns = (config: EmailOnTriggerConfig) => }).reduce((a, [k, v]) => (v === null ? a : { ...a, [k]: v }), {}); export const FT_email = { - [`${functionConfig.collectionPath + [`${`${functionConfig.collectionPath}` .replace(/\//g, "_") .replace(/_{.*?}_/g, "_")}`]: emailOnTriggerFns(functionConfig), }; From a21551bc503029edd3aa02fcfeef367cd8e0a344 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Sat, 29 Aug 2020 09:25:49 +1000 Subject: [PATCH 04/12] slackOnTrigger --- cloud_functions/functions/src/index.ts | 1 + .../functions/src/slackOnTrigger/index.ts | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 cloud_functions/functions/src/slackOnTrigger/index.ts diff --git a/cloud_functions/functions/src/index.ts b/cloud_functions/functions/src/index.ts index bdba22ef..6b03a919 100644 --- a/cloud_functions/functions/src/index.ts +++ b/cloud_functions/functions/src/index.ts @@ -18,3 +18,4 @@ export { FT_compressedThumbnail } from "./compressedThumbnail"; export { actionScript } from "./actionScript"; export { webhook } from "./webhooks"; export { FT_email } from "./emailOnTrigger"; +export { FT_slack } from "./slackOnTrigger"; diff --git a/cloud_functions/functions/src/slackOnTrigger/index.ts b/cloud_functions/functions/src/slackOnTrigger/index.ts new file mode 100644 index 00000000..9fc0287c --- /dev/null +++ b/cloud_functions/functions/src/slackOnTrigger/index.ts @@ -0,0 +1,97 @@ +import { firestore } from "firebase-functions"; +import { hasRequiredFields, serverTimestamp } from "../utils"; +import _config from "../functionConfig"; // generated using generateConfig.ts +import { db } from "../config"; +const functionConfig: any = _config; +type SlackOnTriggerConfig = { + collectionPath: string; + onCreate: Boolean; + onUpdate: Boolean; + onDelete: Boolean; + requiredFields: string[]; + messageDocGenerator: ( + snapshot: + | firestore.DocumentSnapshot + | { + before: firestore.DocumentSnapshot; + after: firestore.DocumentSnapshot; + } + // db: FirebaseFirestore.Firestore + ) => Boolean; +}; +const slackOnCreate = (config: SlackOnTriggerConfig) => + firestore + .document(`${config.collectionPath}/{docId}`) + .onCreate(async (snapshot) => { + try { + const snapshotData = snapshot.data(); + if (!snapshotData) throw Error("no snapshot data"); + const hasAllRequiredFields = hasRequiredFields( + config.requiredFields, + snapshotData + ); + if (hasAllRequiredFields) { + const messageDoc = await config.messageDocGenerator(snapshot); + if (messageDoc && typeof messageDoc === "object") { + await db + .collection("slackBotMessages") + .add({ createdAt: serverTimestamp(), ...messageDoc }); + return true; + } else { + console.log("message is not sent"); + return false; + } + } else { + console.log("requirements were not met"); + return false; + } + } catch (error) { + console.warn(JSON.stringify(error.response.body)); + return false; + } + }); + +const slackOnUpdate = (config: SlackOnTriggerConfig) => + firestore + .document(`${config.collectionPath}/{docId}`) + .onUpdate(async (change) => { + try { + const beforeData = change.before.data(); + const afterData = change.after.data(); + if (!beforeData || !afterData) throw Error("no data found in snapshot"); + const hasAllRequiredFields = hasRequiredFields( + config.requiredFields, + afterData + ); + if (hasAllRequiredFields) { + const messageDoc = await config.messageDocGenerator(change); + if (messageDoc && typeof messageDoc === "object") { + await db + .collection("slackBotMessages") + .add({ createdAt: serverTimestamp(), ...messageDoc }); + return true; + } else { + console.log("message is not sent"); + return false; + } + } else { + console.log("requirements were not met"); + return false; + } + } catch (error) { + console.warn(error); + return false; + } + }); + +const slackOnTriggerFns = (config: SlackOnTriggerConfig) => + Object.entries({ + onCreate: config.onCreate ? slackOnCreate(config) : null, + onUpdate: config.onUpdate ? slackOnUpdate(config) : null, + }).reduce((a, [k, v]) => (v === null ? a : { ...a, [k]: v }), {}); + +export const FT_slack = { + [`${`${functionConfig.collectionPath}` + .replace(/\//g, "_") + .replace(/_{.*?}_/g, "_")}`]: slackOnTriggerFns(functionConfig), +}; From 807d8926f8615fe9a395a2b7d1b13d80bfab1665 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Sat, 29 Aug 2020 09:53:59 +1000 Subject: [PATCH 05/12] console log slack message --- cloud_functions/functions/src/slackOnTrigger/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cloud_functions/functions/src/slackOnTrigger/index.ts b/cloud_functions/functions/src/slackOnTrigger/index.ts index 9fc0287c..e8b4a711 100644 --- a/cloud_functions/functions/src/slackOnTrigger/index.ts +++ b/cloud_functions/functions/src/slackOnTrigger/index.ts @@ -17,7 +17,7 @@ type SlackOnTriggerConfig = { after: firestore.DocumentSnapshot; } // db: FirebaseFirestore.Firestore - ) => Boolean; + ) => Boolean | any; }; const slackOnCreate = (config: SlackOnTriggerConfig) => firestore @@ -65,7 +65,10 @@ const slackOnUpdate = (config: SlackOnTriggerConfig) => ); if (hasAllRequiredFields) { const messageDoc = await config.messageDocGenerator(change); + console.log({ messageDoc }); if (messageDoc && typeof messageDoc === "object") { + console.log("creating slack message doc"); + await db .collection("slackBotMessages") .add({ createdAt: serverTimestamp(), ...messageDoc }); From 730ae4145347d0399bc0be57b74e20a6bd5ab8ca Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Sat, 29 Aug 2020 12:01:23 +1000 Subject: [PATCH 06/12] Duration cell mvp --- .../components/Table/formatters/Duration.tsx | 50 +++++++++++++++++++ www/src/components/Table/formatters/index.tsx | 5 ++ www/src/constants/fields.tsx | 7 ++- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 www/src/components/Table/formatters/Duration.tsx diff --git a/www/src/components/Table/formatters/Duration.tsx b/www/src/components/Table/formatters/Duration.tsx new file mode 100644 index 00000000..d8c0a241 --- /dev/null +++ b/www/src/components/Table/formatters/Duration.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +import { CustomCellProps } from "./withCustomCell"; +import { makeStyles, createStyles } from "@material-ui/core"; + +export const timeDistance = (date1, date2) => { + let distance = Math.abs(date1 - date2); + const hours = Math.floor(distance / 3600000); + distance -= hours * 3600000; + const minutes = Math.floor(distance / 60000); + distance -= minutes * 60000; + const seconds = Math.floor(distance / 1000); + return `${hours ? `${hours}:` : ""}${("0" + minutes).slice(-2)}:${( + "0" + seconds + ).slice(-2)}`; +}; + +const useStyles = makeStyles((theme) => + createStyles({ + root: { + height: "100%", + }, + }) +); + +export default function Duration({ + rowIdx, + column, + value, + onSubmit, +}: CustomCellProps) { + const classes = useStyles(); + const startDate = value.start?.toDate(); + const endDate = value.end?.toDate(); + if (!startDate && !endDate) { + return <>; + } + if (startDate && !endDate) { + const now = new Date(); + const duration = timeDistance(startDate, now); + + return <>{duration}; + } + if (startDate && endDate) { + const duration = timeDistance(endDate, startDate); + + return <>{duration}; + } + return <>; +} diff --git a/www/src/components/Table/formatters/index.tsx b/www/src/components/Table/formatters/index.tsx index e19f1b07..7e85ef5c 100644 --- a/www/src/components/Table/formatters/index.tsx +++ b/www/src/components/Table/formatters/index.tsx @@ -7,6 +7,9 @@ const MultiSelect = lazy(() => import("./MultiSelect" /* webpackChunkName: "MultiSelect" */) ); const DatePicker = lazy(() => import("./Date" /* webpackChunkName: "Date" */)); +const Duration = lazy(() => + import("./Duration" /* webpackChunkName: "Duration" */) +); const Rating = lazy(() => import("./Rating" /* webpackChunkName: "Rating" */)); const Checkbox = lazy(() => import("./Checkbox" /* webpackChunkName: "Checkbox" */) @@ -90,6 +93,8 @@ export const getFormatter = (column: any) => { case FieldType.user: return withCustomCell(User); + case FieldType.duration: + return withCustomCell(Duration); case FieldType.code: return withCustomCell(Code); case FieldType.richText: diff --git a/www/src/constants/fields.tsx b/www/src/constants/fields.tsx index 59f5ad9b..d664ab03 100644 --- a/www/src/constants/fields.tsx +++ b/www/src/constants/fields.tsx @@ -36,7 +36,7 @@ import RichTextIcon from "@material-ui/icons/TextFormat"; import ColorIcon from "@material-ui/icons/Colorize"; import SliderIcon from "assets/icons/Slider"; import UserIcon from "@material-ui/icons/Person"; - +import DurationIcon from "@material-ui/icons/Timer"; export { ShortTextIcon, LongTextIcon, @@ -63,6 +63,7 @@ export { ColorIcon, SliderIcon, UserIcon, + DurationIcon, }; export enum FieldType { @@ -99,6 +100,7 @@ export enum FieldType { color = "COLOR", slider = "SLIDER", user = "USER", + duration = "DURATION", last = "LAST", } @@ -153,6 +155,7 @@ export const FIELDS = [ { icon: , name: "Color", type: FieldType.color }, { icon: , name: "Slider", type: FieldType.slider }, { icon: , name: "User", type: FieldType.user }, + { icon: , name: "Duration", type: FieldType.duration }, ]; /** @@ -169,7 +172,7 @@ export const getFieldIcon = (fieldType: FieldType) => { * @param fieldType */ export const isFieldType = (fieldType: any) => { - const fieldTypes = FIELDS.map(field => field.type); + const fieldTypes = FIELDS.map((field) => field.type); return fieldTypes.includes(fieldType); }; From 6ba4a48781fb2ebc5a1068f79e3a96a83074bd35 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Sun, 30 Aug 2020 00:38:28 +1000 Subject: [PATCH 07/12] slack message document trigger --- cloud_functions/functions/package.json | 1 + cloud_functions/functions/src/index.ts | 1 + .../functions/src/slackOnTrigger/trigger.ts | 87 +++++++++++++++++++ cloud_functions/functions/src/utils/index.ts | 5 ++ cloud_functions/functions/yarn.lock | 85 +++++++++++++++++- 5 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 cloud_functions/functions/src/slackOnTrigger/trigger.ts diff --git a/cloud_functions/functions/package.json b/cloud_functions/functions/package.json index 2f84d7f8..f8bf2a97 100644 --- a/cloud_functions/functions/package.json +++ b/cloud_functions/functions/package.json @@ -20,6 +20,7 @@ "@google-cloud/firestore": "^3.7.5", "@google-cloud/storage": "^5.1.2", "@sendgrid/mail": "^7.2.3", + "@slack/web-api": "^5.11.0", "algoliasearch": "^3.35.1", "firebase-admin": "^8.9.2", "firebase-functions": "^3.3.0", diff --git a/cloud_functions/functions/src/index.ts b/cloud_functions/functions/src/index.ts index 6b03a919..73978e84 100644 --- a/cloud_functions/functions/src/index.ts +++ b/cloud_functions/functions/src/index.ts @@ -19,3 +19,4 @@ export { actionScript } from "./actionScript"; export { webhook } from "./webhooks"; export { FT_email } from "./emailOnTrigger"; export { FT_slack } from "./slackOnTrigger"; +export { slackBotMessageOnCreate } from "./slackOnTrigger/trigger"; diff --git a/cloud_functions/functions/src/slackOnTrigger/trigger.ts b/cloud_functions/functions/src/slackOnTrigger/trigger.ts new file mode 100644 index 00000000..2dd6014d --- /dev/null +++ b/cloud_functions/functions/src/slackOnTrigger/trigger.ts @@ -0,0 +1,87 @@ +import { firestore } from "firebase-functions"; + +import { env } from "../config"; +import { WebClient } from "@slack/web-api"; +import { asyncForEach, serverTimestamp } from "../utils"; +// Initialize +const web = new WebClient(env.slackbot.token); + +const messageByChannel = async ({ + text, + channel, +}: { + channel: string; + text: string; +}) => + await web.chat.postMessage({ + text, + channel, + }); +const messageByEmail = async ({ + email, + text, +}: { + email: string; + text: string; +}) => { + try { + const user = await web.users.lookupByEmail({ email }); + if (user.ok) { + const channel = (user as any).user.id as string; + console.log({ channel }); + return await messageByChannel({ + text, + channel, + }); + } else { + return await false; + } + } catch (error) { + console.log(`${error} maybe${email} is not on slack`); + console.log(`${error}`); + return await false; + } +}; + +export const slackBotMessageOnCreate = firestore + .document(`slackBotMessages/{docId}`) + .onCreate(async (snapshot) => { + const docData = snapshot.data(); + if (!docData) { + return snapshot.ref.update({ + delivered: false, + error: "undefined doc", + }); + } + try { + const channels = docData.channel ? [docData.channel] : docData.channels; + const emails = docData.email ? [docData.email] : docData.emails; + if (channels) { + await asyncForEach(channels, async (channel: string) => { + await messageByChannel({ + text: docData.text, + channel, + }); + }); + } else if (emails) { + await asyncForEach(emails, async (email: string) => { + await messageByEmail({ + text: docData.text, + email, + }); + }); + } + + return snapshot.ref.update({ + delivered: true, + updatedAt: serverTimestamp(), + }); + } catch (error) { + console.log(error); + return snapshot.ref.update({ + delivered: false, + updatedAt: serverTimestamp(), + error: JSON.stringify(error), + }); + } + }); diff --git a/cloud_functions/functions/src/utils/index.ts b/cloud_functions/functions/src/utils/index.ts index 1fd8c200..62ceb48c 100644 --- a/cloud_functions/functions/src/utils/index.ts +++ b/cloud_functions/functions/src/utils/index.ts @@ -15,3 +15,8 @@ export const hasRequiredFields = (requiredFields: string[], data: any) => if (data[currField] === undefined || data[currField] === null) return false; else return acc; }, true); +export async function asyncForEach(array: any[], callback: Function) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} diff --git a/cloud_functions/functions/yarn.lock b/cloud_functions/functions/yarn.lock index cd95009b..0c892ae0 100644 --- a/cloud_functions/functions/yarn.lock +++ b/cloud_functions/functions/yarn.lock @@ -378,6 +378,35 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@slack/logger@>=1.0.0 <3.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-2.0.0.tgz#6a4e1c755849bc0f66dac08a8be54ce790ec0e6b" + integrity sha512-OkIJpiU2fz6HOJujhlhfIGrc8hB4ibqtf7nnbJQDerG0BqwZCfmgtK5sWzZ0TkXVRBKD5MpLrTmCYyMxoMCgPw== + dependencies: + "@types/node" ">=8.9.0" + +"@slack/types@^1.7.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.8.0.tgz#a5a0b31bace03f524174991dfc41c60311e6f32f" + integrity sha512-YvLCtxqbIdCCI+xMQBFH3GJVhRp8jJNl8BUE0RgJlZcDF+wXSB1wkcgLz7zHtD3oOF39GedYiE1e/rQrZ4Dr1A== + +"@slack/web-api@^5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.11.0.tgz#6549ec71d13c2837cc672cbf7793a88eef22802f" + integrity sha512-4a/uj7IZjFLu7Qmq0nH74ecLqk1iI/9x3yRS/v6M5vXDyc5lEruRFp4d5/bz4eN5Bathlq4Bws0wioY516fPag== + dependencies: + "@slack/logger" ">=1.0.0 <3.0.0" + "@slack/types" "^1.7.0" + "@types/is-stream" "^1.1.0" + "@types/node" ">=8.9.0" + "@types/p-queue" "^2.3.2" + axios "^0.19.0" + eventemitter3 "^3.1.0" + form-data "^2.5.0" + is-stream "^1.1.0" + p-queue "^2.4.2" + p-retry "^4.0.0" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -477,6 +506,13 @@ dependencies: "@types/node" "*" +"@types/is-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" + integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== + dependencies: + "@types/node" "*" + "@types/json2csv@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/json2csv/-/json2csv-5.0.1.tgz#576d38515dfedeabf46eb85e790894b8df72ab40" @@ -509,6 +545,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== +"@types/node@>=8.9.0": + version "14.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.2.tgz#264b44c5a28dfa80198fc2f7b6d3c8a054b9491f" + integrity sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A== + "@types/node@^8.10.59": version "8.10.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.60.tgz#73eb4d1e1c8aa5dc724363b57db019cf28863ef7" @@ -519,6 +560,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/p-queue@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8" + integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ== + "@types/qs@*": version "6.9.1" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7" @@ -529,6 +575,11 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/retry@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -853,7 +904,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== -axios@^0.19.2: +axios@^0.19.0, axios@^0.19.2: version "0.19.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== @@ -2205,6 +2256,11 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + events@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -2650,6 +2706,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -4910,11 +4975,24 @@ p-pipe@^3.0.0: resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== +p-queue@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" + integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng== + p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= +p-retry@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.2.0.tgz#ea9066c6b44f23cab4cd42f6147cdbbc6604da5d" + integrity sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.12.0" + p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" @@ -5537,6 +5615,11 @@ retry-request@^4.1.1: debug "^4.1.1" through2 "^3.0.1" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" From 2c21a005221d289a1042037b6494b3803dd532fa Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Tue, 1 Sep 2020 01:05:50 +1000 Subject: [PATCH 08/12] action script configuration --- .../functions/src/functionConfig.ts | 70 ++++++++- www/src/components/Table/ColumnHeader.tsx | 3 +- .../ConfigFields/ColumnSelector.tsx | 8 +- .../ConfigFields/OptionsInput.tsx | 4 +- .../{Settings.tsx => Settings/index.tsx} | 134 ++++++++++++++---- .../components/Table/editors/CodeEditor.tsx | 14 +- .../components/Table/formatters/Action.tsx | 44 +++--- 7 files changed, 215 insertions(+), 62 deletions(-) rename www/src/components/Table/ColumnMenu/{ => Settings}/ConfigFields/ColumnSelector.tsx (81%) rename www/src/components/Table/ColumnMenu/{ => Settings}/ConfigFields/OptionsInput.tsx (97%) rename www/src/components/Table/ColumnMenu/{Settings.tsx => Settings/index.tsx} (70%) diff --git a/cloud_functions/functions/src/functionConfig.ts b/cloud_functions/functions/src/functionConfig.ts index b1eaf699..d8a39771 100644 --- a/cloud_functions/functions/src/functionConfig.ts +++ b/cloud_functions/functions/src/functionConfig.ts @@ -1,2 +1,70 @@ -export default {} as any; +export default { + collectionPath: "feedback", + onCreate: true, + onUpdate: false, + onDelete: false, + requiredFields: ["email", "message", "type", "user", "source", "pagePath"], + messageDocGenerator: (snapshot) => { + const docData = snapshot.data(); + const { email, message, type, user, source, pagePath } = docData; + const { displayName } = user; + console.log({ displayName, email, message, type, user, source, pagePath }); + + // General/Bug/Idea + const generalText = + `Hey!, ` + + displayName + + "(" + + email + + ")" + + ` has the following general feedback for (` + + source.split(".")[0] + + `):*` + + message + + `* `; + const bugText = + `Bug report: *` + + message + + `*\n by *` + + displayName + + "(" + + email + + ")*\n" + + "reported on:" + + source + + pagePath; + const ideaText = + displayName + + "(" + + email + + ")" + + "has an amazing idea! for " + + source.split(".")[0] + + " \n messaged saying:" + + message; + let text = "blank"; + switch (type) { + case "Bug": + text = bugText; + break; + case "Idea": + text = ideaText; + break; + case "General": + text = generalText; + break; + default: + text = "unknown feedback type"; + } + + if (source.includes("education")) { + return { + text, + emails: ["heath@antler.co", "harini@antler.co", "shams@antler.co"], + }; + } else { + return { text, channel: "C01561T4AMB" }; + } + }, +} as any; export const collectionPath = ""; diff --git a/www/src/components/Table/ColumnHeader.tsx b/www/src/components/Table/ColumnHeader.tsx index 61f38e9c..80b80db3 100644 --- a/www/src/components/Table/ColumnHeader.tsx +++ b/www/src/components/Table/ColumnHeader.tsx @@ -18,12 +18,11 @@ import { getFieldIcon } from "constants/fields"; import { useFiretableContext } from "contexts/firetableContext"; import { FiretableOrderBy } from "hooks/useFiretable"; -const useStyles = makeStyles(theme => +const useStyles = makeStyles((theme) => createStyles({ root: { height: "100%", "& svg, & button": { display: "block" }, - color: theme.palette.text.secondary, transition: theme.transitions.create("color", { duration: theme.transitions.duration.short, diff --git a/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx b/www/src/components/Table/ColumnMenu/Settings/ConfigFields/ColumnSelector.tsx similarity index 81% rename from www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx rename to www/src/components/Table/ColumnMenu/Settings/ConfigFields/ColumnSelector.tsx index f83f3821..f59e8e42 100644 --- a/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx +++ b/www/src/components/Table/ColumnMenu/Settings/ConfigFields/ColumnSelector.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { FieldType } from "constants/fields"; import MultiSelect from "@antlerengineering/multiselect"; -import { db } from "../../../../firebase"; +import { db } from "../../../../../firebase"; const ColumnSelector = ({ tableColumns, handleChange, @@ -18,7 +18,7 @@ const ColumnSelector = ({ label?: string; }) => { const [columns, setColumns] = useState(tableColumns ?? []); - const getColumns = async table => { + const getColumns = async (table) => { const tableConfigDoc = await db .doc(`_FIRETABLE_/settings/schema/${table}`) .get(); @@ -33,8 +33,8 @@ const ColumnSelector = ({ }, [table]); const options = columns ? Object.values(columns) - .filter(col => (validTypes ? validTypes.includes(col.type) : true)) - .map(col => ({ value: col.key, label: col.name })) + .filter((col) => (validTypes ? validTypes.includes(col.type) : true)) + .map((col) => ({ value: col.key, label: col.name })) : []; return ( +const useStyles = makeStyles((Theme) => createStyles({ root: {}, field: { @@ -48,7 +48,7 @@ export default function OptionsInput(props: any) { value={newOption} className={classes.field} label={props.placeholder ?? "New Option"} - onChange={e => { + onChange={(e) => { setNewOption(e.target.value); }} onKeyPress={(e: any) => { diff --git a/www/src/components/Table/ColumnMenu/Settings.tsx b/www/src/components/Table/ColumnMenu/Settings/index.tsx similarity index 70% rename from www/src/components/Table/ColumnMenu/Settings.tsx rename to www/src/components/Table/ColumnMenu/Settings/index.tsx index 77f94b42..323616ba 100644 --- a/www/src/components/Table/ColumnMenu/Settings.tsx +++ b/www/src/components/Table/ColumnMenu/Settings/index.tsx @@ -10,6 +10,7 @@ import { TextField, Switch, FormControlLabel, + Divider, } from "@material-ui/core"; import CloseIcon from "@material-ui/icons/Close"; import { FieldType } from "constants/fields"; @@ -17,12 +18,12 @@ import OptionsInput from "./ConfigFields/OptionsInput"; import { useFiretableContext } from "contexts/firetableContext"; import MultiSelect from "@antlerengineering/multiselect"; import _sortBy from "lodash/sortBy"; -import FieldsDropdown from "./FieldsDropdown"; +import FieldsDropdown from "../FieldsDropdown"; import ColumnSelector from "./ConfigFields/ColumnSelector"; import FieldSkeleton from "components/SideDrawer/Form/FieldSkeleton"; import RoleSelector from "components/RolesSelector"; const CodeEditor = lazy(() => - import("../editors/CodeEditor" /* webpackChunkName: "CodeEditor" */) + import("../../editors/CodeEditor" /* webpackChunkName: "CodeEditor" */) ); const ConfigFields = ({ fieldType, config, handleChange, tables, columns }) => { switch (fieldType) { @@ -50,7 +51,7 @@ const ConfigFields = ({ fieldType, config, handleChange, tables, columns }) => { ); case FieldType.connectTable: const tableOptions = _sortBy( - tables?.map(t => ({ + tables?.map((t) => ({ label: `${t.section} - ${t.name}`, value: t.collection, })) ?? [], @@ -77,7 +78,7 @@ const ConfigFields = ({ fieldType, config, handleChange, tables, columns }) => { label="filter template" name="filters" fullWidth - onChange={e => { + onChange={(e) => { handleChange("filters")(e.target.value); }} /> @@ -103,6 +104,48 @@ const ConfigFields = ({ fieldType, config, handleChange, tables, columns }) => { case FieldType.action: return ( <> + Allowed roles + + Authenticated user must have at least one of these to run the script + + + + Required fields + + All of the selected fields must have a value for the script to run + + + + Confirmation Template + + The action button will not ask for confirmation if this is left + empty + + + { + handleChange("confirmation")(e.target.value); + }} + fullWidth + /> { } label="Set as an action script" /> - - - {!Boolean(config.isActionScript) ? ( { + onChange={(e) => { handleChange("callableName")(e.target.value); }} /> ) : ( <> action script - }> + }> + + handleChange("redo.enabled")( + !Boolean(config["redo.enabled"]) + ) + } + name="redo toggle" + /> + } + label="enable redo(reruns the same script)" + /> + + handleChange("undo.enabled")( + !Boolean(config["undo.enabled"]) + ) + } + name="undo toggle" + /> + } + label="enable undo" + /> + {config["undo.enabled"] && ( + <> + + Undo Confirmation Template + + { + handleChange("undo.confirmation")(e.target.value); + }} + fullWidth + /> + Undo Action script + }> + + + + )} )} @@ -178,6 +252,7 @@ const ConfigFields = ({ fieldType, config, handleChange, tables, columns }) => { } handleChange={handleChange("listenerFields")} /> + derivative script }> update => { + handleChange={(key) => (update) => { setNewConfig({ ...newConfig, [key]: update }); }} config={newConfig} diff --git a/www/src/components/Table/editors/CodeEditor.tsx b/www/src/components/Table/editors/CodeEditor.tsx index da9c9a35..aa43a228 100644 --- a/www/src/components/Table/editors/CodeEditor.tsx +++ b/www/src/components/Table/editors/CodeEditor.tsx @@ -5,7 +5,7 @@ import { useFiretableContext } from "contexts/firetableContext"; import { FieldType } from "constants/fields"; import { setTimeout } from "timers"; -const useStyles = makeStyles(theme => +const useStyles = makeStyles((theme) => createStyles({ editorWrapper: { position: "relative", minWidth: 800 }, @@ -31,7 +31,7 @@ const useStyles = makeStyles(theme => ); export default function CodeEditor(props: any) { - const { handleChange, script } = props; + const { handleChange, script, height = "90hv" } = props; const [initialEditorValue] = useState(script ?? ""); const { tableState } = useFiretableContext(); @@ -45,7 +45,7 @@ export default function CodeEditor(props: any) { function listenEditorChanges() { setTimeout(() => { - editorRef.current?.onDidChangeModelContent(ev => { + editorRef.current?.onDidChangeModelContent((ev) => { handleChange(editorRef.current.getValue()); }); }, 2000); @@ -63,7 +63,7 @@ export default function CodeEditor(props: any) { // console.timeLog(firebaseAuthDefs); monaco .init() - .then(monacoInstance => { + .then((monacoInstance) => { monacoInstance.languages.typescript.javascriptDefaults.addExtraLib( firestoreDefs ); @@ -122,7 +122,7 @@ export default function CodeEditor(props: any) { return `static ${columnKey}:string`; case FieldType.singleSelect: const typeString = [ - ...column.config.options.map(opt => `"${opt}"`), + ...column.config.options.map((opt) => `"${opt}"`), // "string", ].join(" | "); return `static ${columnKey}:${typeString}`; @@ -140,7 +140,7 @@ export default function CodeEditor(props: any) { ); // monacoInstance.editor.create(wrapper, properties); }) - .catch(error => + .catch((error) => console.error( "An error occurred during initialization of Monaco: ", error @@ -154,7 +154,7 @@ export default function CodeEditor(props: any) {
{/*
*/} +const useStyles = makeStyles((theme) => createStyles({ root: { padding: theme.spacing(0, 0.375, 0, 1.5) }, labelContainer: { overflowX: "hidden" }, @@ -32,6 +32,16 @@ const replacer = (data: any) => (m: string, key: string) => { return _get(data, objKey, defaultValue); }; +const getStateIcon = (actionState) => { + switch (actionState) { + case "undo": + return ; + case "redo": + return ; + default: + return ; + } +}; export default function Action({ column, row, @@ -66,13 +76,13 @@ export default function Action({ cloudFunction( callableName, data, - response => { + (response) => { const { message, cellValue, success } = response.data; setIsRunning(false); snack.open({ message, severity: success ? "success" : "error" }); if (cellValue) onSubmit(cellValue); }, - error => { + (error) => { console.error("ERROR", callableName, error); setIsRunning(false); snack.open({ message: JSON.stringify(error), severity: "error" }); @@ -80,6 +90,12 @@ export default function Action({ ); }; const hasRan = value && value.status; + + const actionState: "run" | "undo" | "redo" = hasRan + ? value.undo + ? "undo" + : "redo" + : "run"; let component = ( {isRunning ? ( - ) : hasRan ? ( - value.undo ? ( - - ) : ( - - ) ) : ( - + getStateIcon(actionState) )} ); - if ((column as any)?.config?.confirmation) + if (typeof config.confirmation === "string") { component = ( {component} ); - + } return ( Date: Tue, 1 Sep 2020 12:59:50 +1000 Subject: [PATCH 09/12] add db ref in emailOnTrigger --- cloud_functions/functions/src/emailOnTrigger/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts index ec2222cd..79eb6f45 100644 --- a/cloud_functions/functions/src/emailOnTrigger/index.ts +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -2,6 +2,7 @@ import { firestore } from "firebase-functions"; import { sendEmail } from "../utils/email"; import { hasRequiredFields } from "../utils"; +import { db } from "../config"; import _config from "../functionConfig"; // generated using generateConfig.ts const functionConfig: any = _config; type EmailOnTriggerConfig = { @@ -85,8 +86,8 @@ const emailOnUpdate = (config: EmailOnTriggerConfig) => {} ); if (shouldSend && hasAllRequiredFields) { - const from = await config.from(afterData); - const to = await config.to(afterData); + const from = await config.from(afterData, db); + const to = await config.to(afterData, db); const msg = { from, personalizations: [ From fbf3952a5ec32a7e69533f8897f15cebc3030db7 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Tue, 1 Sep 2020 14:13:16 +1000 Subject: [PATCH 10/12] debug email trigger recipient --- cloud_functions/functions/src/emailOnTrigger/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts index 79eb6f45..b34bc755 100644 --- a/cloud_functions/functions/src/emailOnTrigger/index.ts +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -88,6 +88,7 @@ const emailOnUpdate = (config: EmailOnTriggerConfig) => if (shouldSend && hasAllRequiredFields) { const from = await config.from(afterData, db); const to = await config.to(afterData, db); + console.log(JSON.stringify({ to })); const msg = { from, personalizations: [ From 203c23ed693bc4a02868aafe414eb721cc5d9204 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Tue, 1 Sep 2020 14:20:02 +1000 Subject: [PATCH 11/12] add db ref for onCreate email trigger --- cloud_functions/functions/src/emailOnTrigger/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloud_functions/functions/src/emailOnTrigger/index.ts b/cloud_functions/functions/src/emailOnTrigger/index.ts index b34bc755..48671774 100644 --- a/cloud_functions/functions/src/emailOnTrigger/index.ts +++ b/cloud_functions/functions/src/emailOnTrigger/index.ts @@ -36,8 +36,9 @@ const emailOnCreate = (config: EmailOnTriggerConfig) => config.requiredFields, snapshotData ); - const to = await config.to(snapshotData); - const from = await config.from(snapshotData); + const to = await config.to(snapshotData, db); + const from = await config.from(snapshotData, db); + console.log(JSON.stringify({ to, from })); if (shouldSend && hasAllRequiredFields) { const msg = { from, From 11612b879eb1fb52807aefae1f0da39543a5a980 Mon Sep 17 00:00:00 2001 From: Shams mosowi Date: Tue, 1 Sep 2020 15:16:33 +1000 Subject: [PATCH 12/12] actionscript callable updated --- .../functions/src/actionScript/index.ts | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/cloud_functions/functions/src/actionScript/index.ts b/cloud_functions/functions/src/actionScript/index.ts index 65f8b3d1..b2b904cc 100644 --- a/cloud_functions/functions/src/actionScript/index.ts +++ b/cloud_functions/functions/src/actionScript/index.ts @@ -28,7 +28,7 @@ const missingFieldsReducer = (data: any) => (acc: string[], curr: string) => { } else return acc; }; -const generateSchemaDocPath = tablePath => { +const generateSchemaDocPath = (tablePath) => { const pathComponents = tablePath.split("/"); return `_FIRETABLE_/settings/${ pathComponents[1] === "table" ? "schema" : "groupSchema" @@ -43,7 +43,7 @@ export const actionScript = functions.https.onCall( throw Error(`You are unauthenticated`); } - const { ref, row, column } = data; + const { ref, row, column, action } = data; const schemaDocPath = generateSchemaDocPath(ref.tablePath); const schemaDoc = await db.doc(schemaDocPath).get(); @@ -51,13 +51,16 @@ export const actionScript = functions.https.onCall( if (!schemaDocData) { return { success: false, - message: "no schema found", }; } - const { script, requiredRoles, requiredFields } = schemaDocData.columns[ - column.key - ].config; + const { + script, + requiredRoles, + requiredFields, + undo, + redo, + } = schemaDocData.columns[column.key].config; if (!hasAnyRole(requiredRoles, context)) { throw Error(`You don't have the required roles permissions`); } @@ -89,24 +92,34 @@ export const actionScript = functions.https.onCall( message: string; status: string; success: boolean; - } = await eval(`async({db, auth, utilFns})=>{${script}}`)({ + } = await eval( + `async({db, auth, utilFns})=>{${ + action === "undo" ? undo.script : script + }}` + )({ db, auth, utilFns, }); - return { - success: result.success, - message: result.message, - cellValue: { - redo: true, - status: result.status, - completedAt: serverTimestamp(), - meta: { ranBy: context.auth!.token.email }, - undo: false, - }, - undo: false, - redo: false, - }; + if (result.success) + return { + success: result.success, + message: result.message, + cellValue: { + redo: redo.enabled, + status: result.status, + completedAt: serverTimestamp(), + meta: { ranBy: context.auth!.token.email }, + undo: undo.enabled, + }, + undo: undo.enabled, + redo: redo.enabled, + }; + else + return { + success: false, + message: result.message, + }; } catch (error) { return { success: false,