mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
Merge pull request #345 from AntlerVC/feature/cloud-run
Feature/cloud run
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
steps:
|
||||
- name: node:14.9.0
|
||||
entrypoint: yarn
|
||||
args: ["install"]
|
||||
dir: "FT_functions/compiler"
|
||||
- name: node:14.9.0
|
||||
entrypoint: yarn
|
||||
args:
|
||||
- "compile"
|
||||
- "${_SCHEMA_PATH}"
|
||||
dir: "FT_functions/compiler"
|
||||
- name: node:14.9.0
|
||||
entrypoint: yarn
|
||||
args: ["install"]
|
||||
dir: "FT_functions/functions"
|
||||
- name: node:14.9.0
|
||||
entrypoint: yarn
|
||||
args:
|
||||
- "deployFT"
|
||||
- "--project"
|
||||
- "${_PROJECT_ID}"
|
||||
- "--token"
|
||||
- "${_FIREBASE_TOKEN}"
|
||||
- "--only"
|
||||
- "functions"
|
||||
dir: "FT_functions/functions"
|
||||
|
||||
substitutions:
|
||||
_PROJECT_ID: "project-id" # default value
|
||||
options:
|
||||
machineType: "N1_HIGHCPU_8"
|
||||
66
FT_functions/.gitignore
vendored
66
FT_functions/.gitignore
vendored
@@ -1,66 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
firebase-debug.log*
|
||||
firebase-debug.*.log*
|
||||
|
||||
# Firebase cache
|
||||
.firebase/
|
||||
|
||||
# Firebase config
|
||||
|
||||
# Uncomment this if you'd like others to create their own Firebase project.
|
||||
# For a team working on the same Firebase project(s), it is recommended to leave
|
||||
# it commented so all members can deploy to the same project(s) in .firebaserc.
|
||||
# .firebaserc
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
@@ -1,28 +0,0 @@
|
||||
import { addPackages, addSparkLib } from "./terminal";
|
||||
const fs = require("fs");
|
||||
import { generateConfigFromTableSchema } from "./loader";
|
||||
|
||||
async function asyncForEach(array: any[], callback: Function) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
generateConfigFromTableSchema(process.argv[2]).then(async () => {
|
||||
const configFile = fs.readFileSync(
|
||||
"../functions/src/functionConfig.ts",
|
||||
"utf-8"
|
||||
);
|
||||
const requiredDependencies = configFile.match(
|
||||
/(?<=(require\(("|'))).*?(?=("|')\))/g
|
||||
);
|
||||
if (requiredDependencies) {
|
||||
await addPackages(requiredDependencies.map((p) => ({ name: p })));
|
||||
}
|
||||
|
||||
const { sparksConfig } = require("../functions/src/functionConfig");
|
||||
const requiredSparks = sparksConfig.map((s) => s.type);
|
||||
console.log({ requiredSparks });
|
||||
|
||||
await asyncForEach(requiredSparks, async (s) => await addSparkLib(s));
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "firetable-functions-compiler",
|
||||
"scripts": {
|
||||
"compile": "ts-node index"
|
||||
},
|
||||
"engines": {
|
||||
"node": "14"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"firebase-admin": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.11",
|
||||
"firebase-tools": "^8.7.0",
|
||||
"husky": "^4.2.5",
|
||||
"js-beautify": "^1.13.5",
|
||||
"prettier": "^2.1.1",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"ts-node": "^8.6.2",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged"
|
||||
}
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import * as child from "child_process";
|
||||
|
||||
function execute(command, callback) {
|
||||
child.exec(command, function (error, stdout, stderr) {
|
||||
console.log({ error, stdout, stderr });
|
||||
callback(stdout);
|
||||
});
|
||||
}
|
||||
|
||||
export const addPackages = (packages: { name: string; version?: string }[]) =>
|
||||
new Promise((resolve, reject) => {
|
||||
//const command =`cd FT_functions/functions;yarn add ${packageName}@${version}`
|
||||
const packagesString = packages.reduce((acc, currPackage) => {
|
||||
return `${acc} ${currPackage.name}@${currPackage.version ?? "latest"}`;
|
||||
}, "");
|
||||
if (packagesString.trim().length !== 0) {
|
||||
execute("ls", function () {});
|
||||
|
||||
const command = `cd ../functions;yarn add ${packagesString}`;
|
||||
console.log(command);
|
||||
execute(command, function () {
|
||||
resolve(true);
|
||||
});
|
||||
} else resolve(false);
|
||||
});
|
||||
|
||||
export const addSparkLib = (name: string) =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
const { dependencies } = require(`../sparksLib/${name}`);
|
||||
const packages = Object.keys(dependencies).map((key) => ({
|
||||
name: key,
|
||||
version: dependencies[key],
|
||||
}));
|
||||
await addPackages(packages);
|
||||
const command = `cp ../sparksLib/${name}.ts ../functions/src/sparks/${name}.ts`;
|
||||
execute(command, function () {
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ["plugin:import/errors", "plugin:import/warnings"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "import"],
|
||||
rules: {
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
"@typescript-eslint/no-empty-function": "error",
|
||||
"@typescript-eslint/no-empty-interface": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/triple-slash-reference": "error",
|
||||
"@typescript-eslint/unified-signatures": "warn",
|
||||
"comma-dangle": "warn",
|
||||
"constructor-super": "error",
|
||||
eqeqeq: ["warn", "always"],
|
||||
"import/no-deprecated": "warn",
|
||||
"import/no-extraneous-dependencies": "error",
|
||||
"import/no-unassigned-import": "warn",
|
||||
"no-cond-assign": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-empty": [
|
||||
"error",
|
||||
{
|
||||
allowEmptyCatch: true,
|
||||
},
|
||||
],
|
||||
"no-invalid-this": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": [
|
||||
"error",
|
||||
{
|
||||
hoist: "all",
|
||||
},
|
||||
],
|
||||
"no-throw-literal": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-var": "warn",
|
||||
"no-void": "error",
|
||||
"prefer-const": "warn",
|
||||
},
|
||||
settings: {
|
||||
jsdoc: {
|
||||
tagNamePreference: {
|
||||
returns: "return",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
66
cloud_build_functions/.gitignore
vendored
66
cloud_build_functions/.gitignore
vendored
@@ -1,66 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
firebase-debug.log*
|
||||
firebase-debug.*.log*
|
||||
|
||||
# Firebase cache
|
||||
.firebase/
|
||||
|
||||
# Firebase config
|
||||
|
||||
# Uncomment this if you'd like others to create their own Firebase project.
|
||||
# For a team working on the same Firebase project(s), it is recommended to leave
|
||||
# it commented so all members can deploy to the same project(s) in .firebaserc.
|
||||
# .firebaserc
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"functions": {
|
||||
"predeploy": [
|
||||
"npm --prefix \"$RESOURCE_DIR\" run lint",
|
||||
"npm --prefix \"$RESOURCE_DIR\" run build"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "import"],
|
||||
rules: {
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
"@typescript-eslint/no-empty-function": "error",
|
||||
"@typescript-eslint/no-empty-interface": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/triple-slash-reference": "error",
|
||||
"@typescript-eslint/unified-signatures": "warn",
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"constructor-super": "error",
|
||||
eqeqeq: ["warn", "always"],
|
||||
"import/no-deprecated": "warn",
|
||||
"import/no-extraneous-dependencies": "error",
|
||||
"import/no-unassigned-import": "warn",
|
||||
"no-cond-assign": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-empty": [
|
||||
"error",
|
||||
{
|
||||
allowEmptyCatch: true,
|
||||
},
|
||||
],
|
||||
"no-invalid-this": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": [
|
||||
"error",
|
||||
{
|
||||
hoist: "all",
|
||||
},
|
||||
],
|
||||
"no-throw-literal": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-var": "warn",
|
||||
"no-void": "error",
|
||||
"prefer-const": "warn",
|
||||
},
|
||||
settings: {
|
||||
jsdoc: {
|
||||
tagNamePreference: {
|
||||
returns: "return",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
12
cloud_build_functions/functions/.gitignore
vendored
12
cloud_build_functions/functions/.gitignore
vendored
@@ -1,12 +0,0 @@
|
||||
# Compiled JavaScript files
|
||||
**/*.js
|
||||
**/*.js.map
|
||||
|
||||
# Except the ESLint config file
|
||||
!.eslintrc.js
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Node.js dependency directory
|
||||
node_modules/
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*\"",
|
||||
"build": "tsc",
|
||||
"serve": "npm run build && firebase emulators:start --only functions",
|
||||
"shell": "npm run build && firebase functions:shell",
|
||||
"start": "npm run shell",
|
||||
"deploy": "firebase deploy --only functions",
|
||||
"logs": "firebase functions:log"
|
||||
},
|
||||
"engines": {
|
||||
"node": "12"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/cloudbuild": "^2.0.6",
|
||||
"firebase-admin": "^9.2.0",
|
||||
"firebase-functions": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^3.9.1",
|
||||
"@typescript-eslint/parser": "^3.8.0",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"firebase-functions-test": "^0.2.0",
|
||||
"typescript": "^3.8.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Initialize Firebase Admin
|
||||
import * as functions from "firebase-functions";
|
||||
import * as admin from "firebase-admin";
|
||||
admin.initializeApp();
|
||||
|
||||
// Initialize Cloud Firestore Database
|
||||
export const db = admin.firestore();
|
||||
// Initialize Auth
|
||||
export const auth = admin.auth();
|
||||
|
||||
const settings = { timestampsInSnapshots: true };
|
||||
db.settings(settings);
|
||||
export const env = functions.config();
|
||||
@@ -1,82 +0,0 @@
|
||||
import * as functions from "firebase-functions";
|
||||
import { hasAnyRole } from "./utils/auth";
|
||||
//import { serverTimestamp } from "./utils";
|
||||
import { db } from "./firebaseConfig";
|
||||
const { CloudBuildClient } = require("@google-cloud/cloudbuild");
|
||||
const cb = new CloudBuildClient();
|
||||
|
||||
export const FT_triggerCloudBuild = functions.https.onCall(
|
||||
async (
|
||||
data: {
|
||||
schemaPath: string;
|
||||
},
|
||||
context: functions.https.CallableContext
|
||||
) => {
|
||||
try {
|
||||
const authorized = hasAnyRole(["ADMIN"], context);
|
||||
const { schemaPath } = data;
|
||||
const firetableSettingsDoc = await db.doc("_FIRETABLE_/settings").get();
|
||||
const firetableSettings = firetableSettingsDoc.data();
|
||||
if (!firetableSettings) throw Error("Error: firetableSettings not found");
|
||||
const { triggerId, branch } = firetableSettings.cloudBuild;
|
||||
if (!context.auth || !authorized) {
|
||||
console.warn(`unauthorized user${context}`);
|
||||
return {
|
||||
success: false,
|
||||
message: "you don't have permission to trigger a build",
|
||||
};
|
||||
}
|
||||
// Starts a build against the branch provided.
|
||||
const [resp] = await cb.runBuildTrigger({
|
||||
projectId: process.env.GCLOUD_PROJECT, //project hosting cloud build
|
||||
triggerId,
|
||||
source: {
|
||||
branchName: branch,
|
||||
substitutions: {
|
||||
_PROJECT_ID: process.env.GCLOUD_PROJECT,
|
||||
_SCHEMA_PATH: schemaPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
const buildId = resp.metadata.build.id;
|
||||
const logUrl = resp.metadata.build.logUrl;
|
||||
|
||||
await db.doc(schemaPath).update({ cloudBuild: { logUrl, buildId } });
|
||||
console.log({ buildId, logUrl });
|
||||
|
||||
if (buildId && logUrl) {
|
||||
return {
|
||||
message: "Deploying latest configuration",
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
return {
|
||||
message: err,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const FT_cloudBuildUpdates = functions.pubsub
|
||||
.topic("cloud-builds")
|
||||
.onPublish(async (message, context) => {
|
||||
console.log(JSON.stringify(message));
|
||||
const { buildId, status } = message.attributes;
|
||||
console.log(JSON.stringify({ buildId, status }));
|
||||
//message
|
||||
//status: "SUCCESS"
|
||||
//buildId: "1a6d7819-aa35-486c-a29c-fb67eb39430f"
|
||||
|
||||
const query = await db
|
||||
.collection("_FIRETABLE_/settings/schema")
|
||||
.where("cloudBuild.buildId", "==", buildId)
|
||||
.get();
|
||||
|
||||
if (query.docs.length !== 0) {
|
||||
await query.docs[0].ref.update({ "cloudBuild.status": status });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as functions from "firebase-functions";
|
||||
|
||||
export const hasAnyRole = (
|
||||
authorizedRoles: string[],
|
||||
context: functions.https.CallableContext
|
||||
) => {
|
||||
if (!context.auth || !context.auth.token.roles) return false;
|
||||
const userRoles = context.auth.token.roles as string[];
|
||||
const authorization = authorizedRoles.reduce(
|
||||
(authorized: boolean, role: string) => {
|
||||
if (userRoles.includes(role)) return true;
|
||||
else return authorized;
|
||||
},
|
||||
false
|
||||
);
|
||||
return authorization;
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import * as admin from "firebase-admin";
|
||||
|
||||
export const serverTimestamp = admin.firestore.FieldValue.serverTimestamp;
|
||||
// import { sendEmail } from "./email";
|
||||
// import { hasAnyRole } from "./auth";
|
||||
// import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
|
||||
|
||||
// const secrets = new SecretManagerServiceClient();
|
||||
|
||||
// export const getSecret = async (name: string, v: string = "latest") => {
|
||||
// const [version] = await secrets.accessSecretVersion({
|
||||
// name: `projects/${process.env.GCLOUD_PROJECT}/secrets/${name}/versions/${v}`,
|
||||
// });
|
||||
// const payload = version.payload?.data?.toString();
|
||||
// if (payload && payload[0] === "{") {
|
||||
// return JSON.parse(payload);
|
||||
// } else {
|
||||
// return payload;
|
||||
// }
|
||||
// };
|
||||
// const characters =
|
||||
// "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
// export function generateId(length: number): string {
|
||||
// let result = "";
|
||||
// const charactersLength = characters.length;
|
||||
// for (let i = 0; i < length; i++) {
|
||||
// result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// 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);
|
||||
// async function asyncForEach(array: any[], callback: Function) {
|
||||
// for (let index = 0; index < array.length; index++) {
|
||||
// await callback(array[index], index, array);
|
||||
// }
|
||||
// }
|
||||
// export const getTriggerType = (change) =>
|
||||
// Boolean(change.after.data()) && Boolean(change.before.data())
|
||||
// ? "update"
|
||||
// : Boolean(change.after.data())
|
||||
// ? "create"
|
||||
// : "delete";
|
||||
|
||||
// export default {
|
||||
// getSecret,
|
||||
// hasRequiredFields,
|
||||
// generateId,
|
||||
// sendEmail,
|
||||
// serverTimestamp,
|
||||
// hasAnyRole,
|
||||
// asyncForEach,
|
||||
// };
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "es2017"
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -324,11 +324,13 @@ crypto-random-string@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
|
||||
date-and-time@^0.14.2:
|
||||
version "0.14.2"
|
||||
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.14.2.tgz#a4266c3dead460f6c231fe9674e585908dac354e"
|
||||
integrity sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA==
|
||||
|
||||
|
||||
debug@4, debug@^4.1.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
|
||||
|
||||
2
ft_build/. dockerignore
Normal file
2
ft_build/. dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
antler*.json
|
||||
.gitignore
|
||||
1
ft_build/.gitignore
vendored
Normal file
1
ft_build/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/
|
||||
20
ft_build/Dockerfile
Normal file
20
ft_build/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
# Use the official lightweight Node.js image.
|
||||
# https://hub.docker.com/_/node
|
||||
FROM node:14-slim
|
||||
|
||||
# Create and change to the app directory.
|
||||
WORKDIR /workdir
|
||||
|
||||
# Copy local code to the container image.
|
||||
# If you've done yarn install locally, node_modules will be copied to
|
||||
# docker work directory to save time to perform the same actions again.
|
||||
COPY . ./
|
||||
|
||||
# Install production missing dependencies from above copy command.
|
||||
# If you add a package-lock.json, speed your build by switching to 'npm ci'.
|
||||
# RUN npm ci --only=production
|
||||
RUN yarn
|
||||
|
||||
# Run the web service on container startup.
|
||||
CMD [ "yarn", "start" ]
|
||||
63
ft_build/compiler/index.ts
Normal file
63
ft_build/compiler/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { addPackages, addSparkLib, asyncExecute } from "./terminal";
|
||||
const fs = require("fs");
|
||||
import { generateConfigFromTableSchema } from "./loader";
|
||||
import { commandErrorHandler } from "../utils";
|
||||
const path = require("path");
|
||||
import admin from "firebase-admin";
|
||||
|
||||
export default async function generateConfig(
|
||||
schemaPath: string,
|
||||
user: admin.auth.UserRecord
|
||||
) {
|
||||
return await generateConfigFromTableSchema(schemaPath, user).then(
|
||||
async (success) => {
|
||||
if (!success) {
|
||||
console.log("generateConfigFromTableSchema failed to complete");
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("generateConfigFromTableSchema done");
|
||||
const configFile = fs.readFileSync(
|
||||
path.resolve(__dirname, "../functions/src/functionConfig.ts"),
|
||||
"utf-8"
|
||||
);
|
||||
const requiredDependencies = configFile.match(
|
||||
/(?<=(require\(("|'))).*?(?=("|')\))/g
|
||||
);
|
||||
if (requiredDependencies) {
|
||||
const packgesAdded = await addPackages(
|
||||
requiredDependencies.map((p: any) => ({ name: p })),
|
||||
user
|
||||
);
|
||||
if (!packgesAdded) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const isFunctionConfigValid = await asyncExecute(
|
||||
"cd build/functions/src; tsc functionConfig.ts",
|
||||
commandErrorHandler({
|
||||
user,
|
||||
functionConfigTs: configFile,
|
||||
description: `Invalid compiled functionConfig.ts`,
|
||||
})
|
||||
);
|
||||
if (!isFunctionConfigValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { sparksConfig } = require("../functions/src/functionConfig.js");
|
||||
const requiredSparks = sparksConfig.map((s: any) => s.type);
|
||||
console.log({ requiredSparks });
|
||||
|
||||
for (const lib of requiredSparks) {
|
||||
const success = await addSparkLib(lib, user);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
import { db } from "../firebaseConfig";
|
||||
const fs = require("fs");
|
||||
const beautify = require("js-beautify").js;
|
||||
// Initialize Firebase Admin
|
||||
import * as admin from "firebase-admin";
|
||||
// Initialize Firebase Admin
|
||||
//const serverTimestamp = admin.firestore.FieldValue.serverTimestamp;
|
||||
import admin from "firebase-admin";
|
||||
import { parseSparksConfig } from "../utils";
|
||||
|
||||
admin.initializeApp();
|
||||
//const serviceAccount = require("./antler-vc-firebase.json");
|
||||
//admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
|
||||
const db = admin.firestore();
|
||||
|
||||
export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
export const generateConfigFromTableSchema = async (
|
||||
schemaDocPath: string,
|
||||
user: admin.auth.UserRecord
|
||||
) => {
|
||||
const schemaDoc = await db.doc(schemaDocPath).get();
|
||||
const schemaData = schemaDoc.data();
|
||||
if (!schemaData) throw new Error("no schema found");
|
||||
@@ -35,7 +32,7 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
}',evaluate:async ({row,ref,db,auth,utilFns}) =>{${
|
||||
currColumn.config.script
|
||||
}},\nlistenerFields:[${currColumn.config.listenerFields
|
||||
.map((fieldKey) => `"${fieldKey}"`)
|
||||
.map((fieldKey: string) => `"${fieldKey}"`)
|
||||
.join(",\n")}]},\n`;
|
||||
},
|
||||
""
|
||||
@@ -77,13 +74,14 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
return `${acc}{\nfieldName:'${
|
||||
currColumn.key
|
||||
}',\ntrackedFields:[${currColumn.config.trackedFields
|
||||
.map((fieldKey) => `"${fieldKey}"`)
|
||||
.map((fieldKey: string) => `"${fieldKey}"`)
|
||||
.join(",\n")}]},\n`;
|
||||
},
|
||||
""
|
||||
)}]`;
|
||||
|
||||
const sparksConfig = schemaData.sparks ? schemaData.sparks : "[]";
|
||||
const sparksConfig = parseSparksConfig(schemaData.sparks, user);
|
||||
|
||||
const collectionType = schemaDocPath.includes("subTables")
|
||||
? "subCollection"
|
||||
: schemaDocPath.includes("groupSchema")
|
||||
@@ -94,7 +92,7 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
let triggerPath = "";
|
||||
switch (collectionType) {
|
||||
case "collection":
|
||||
collectionId = schemaDocPath.split("/").pop();
|
||||
collectionId = schemaDocPath.split("/").pop() ?? "";
|
||||
functionName = `"${collectionId}"`;
|
||||
triggerPath = `"${collectionId}/{docId}"`;
|
||||
break;
|
||||
@@ -118,7 +116,7 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
'"';
|
||||
break;
|
||||
case "groupCollection":
|
||||
collectionId = schemaDocPath.split("/").pop();
|
||||
collectionId = schemaDocPath.split("/").pop() ?? "";
|
||||
const triggerDepth = schemaData.triggerDepth
|
||||
? schemaData.triggerDepth
|
||||
: 1;
|
||||
@@ -134,7 +132,7 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const exports = {
|
||||
const exports: any = {
|
||||
triggerPath,
|
||||
functionName: functionName.replace(/-/g, "_"),
|
||||
derivativesConfig,
|
||||
@@ -146,8 +144,12 @@ export const generateConfigFromTableSchema = async (schemaDocPath) => {
|
||||
const fileData = Object.keys(exports).reduce((acc, currKey) => {
|
||||
return `${acc}\nexport const ${currKey} = ${exports[currKey]}`;
|
||||
}, ``);
|
||||
|
||||
const path = require("path");
|
||||
fs.writeFileSync(
|
||||
"../functions/src/functionConfig.ts",
|
||||
path.resolve(__dirname, "../functions/src/functionConfig.ts"),
|
||||
beautify(fileData, { indent_size: 2 })
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
63
ft_build/compiler/terminal.ts
Normal file
63
ft_build/compiler/terminal.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as child from "child_process";
|
||||
import admin from "firebase-admin";
|
||||
import { commandErrorHandler } from "../utils";
|
||||
|
||||
function execute(command: string, callback: any) {
|
||||
console.log(command);
|
||||
child.exec(command, function (error, stdout, stderr) {
|
||||
console.log({ error, stdout, stderr });
|
||||
callback(stdout);
|
||||
});
|
||||
}
|
||||
|
||||
export const asyncExecute = async (command: string, callback: any) =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
child.exec(command, async function (error, stdout, stderr) {
|
||||
console.log({ error, stdout, stderr });
|
||||
await callback(error, stdout, stderr);
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
|
||||
export const addPackages = async (
|
||||
packages: { name: string; version?: string }[],
|
||||
user: admin.auth.UserRecord
|
||||
) => {
|
||||
const packagesString = packages.reduce((acc, currPackage) => {
|
||||
return `${acc} ${currPackage.name}@${currPackage.version ?? "latest"}`;
|
||||
}, "");
|
||||
if (packagesString.trim().length !== 0) {
|
||||
const success = await asyncExecute(
|
||||
`cd build/functions;yarn add ${packagesString}`,
|
||||
commandErrorHandler({
|
||||
user,
|
||||
description: "Error adding packages",
|
||||
})
|
||||
);
|
||||
return success;
|
||||
}
|
||||
};
|
||||
|
||||
export const addSparkLib = async (
|
||||
name: string,
|
||||
user: admin.auth.UserRecord
|
||||
) => {
|
||||
const { dependencies } = require(`../sparksLib/${name}`);
|
||||
const packages = Object.keys(dependencies).map((key) => ({
|
||||
name: key,
|
||||
version: dependencies[key],
|
||||
}));
|
||||
let success = await addPackages(packages, user);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
success = await asyncExecute(
|
||||
`cp build/sparksLib/${name}.ts build/functions/src/sparks/${name}.ts`,
|
||||
commandErrorHandler({
|
||||
user,
|
||||
description: "Error copying sparksLib",
|
||||
})
|
||||
);
|
||||
return success;
|
||||
};
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"include": ["src", "generateConfig.ts"],
|
||||
"ignore": ["sparks"]
|
||||
"ignore": ["sparks", "sparksLib"]
|
||||
}
|
||||
34
ft_build/deploy.sh
Executable file
34
ft_build/deploy.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
helpFunction()
|
||||
{
|
||||
echo "Usage: ./deploy.sh --firebase-token [YOUR FIREBASE TOKEN] --project-id [YOUR GCLOUD PROJECT ID]"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while test $# -gt 0; do
|
||||
case "$1" in
|
||||
--firebase-token)
|
||||
shift
|
||||
firebase_token=$1
|
||||
shift
|
||||
;;
|
||||
--project-id)
|
||||
shift
|
||||
project_id=$1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "$1 is not a recognized flag!"
|
||||
return 1;
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$firebase_token" || -z "$project_id" ]];
|
||||
then
|
||||
helpFunction
|
||||
fi
|
||||
|
||||
gcloud builds submit --tag gcr.io/$project_id/ft-builder
|
||||
gcloud run deploy ft-builder --image gcr.io/$project_id/ft-builder --platform managed --memory 4Gi --allow-unauthenticated --set-env-vars="_FIREBASE_TOKEN=$firebase_token,_PROJECT_ID=$project_id"
|
||||
10
ft_build/firebaseConfig.ts
Normal file
10
ft_build/firebaseConfig.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// Initialize Firebase Admin
|
||||
import * as admin from "firebase-admin";
|
||||
|
||||
admin.initializeApp();
|
||||
const db = admin.firestore();
|
||||
const auth = admin.auth();
|
||||
|
||||
db.settings({ timestampsInSnapshots: true, ignoreUndefinedProperties: true });
|
||||
|
||||
export { db, admin, auth };
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"build": "tsc",
|
||||
@@ -20,11 +21,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.11",
|
||||
"firebase-tools": "^9.2.2",
|
||||
"husky": "^4.2.5",
|
||||
"prettier": "^2.1.1",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"ts-node": "^8.6.2",
|
||||
"tsc": "^1.20150623.0",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
@@ -51,7 +51,7 @@ const updateLinks = (
|
||||
);
|
||||
return Promise.all([...addPromises, ...removePromises]);
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
};
|
||||
export default function propagate(
|
||||
@@ -59,16 +59,16 @@ export default function propagate(
|
||||
config: { fieldName: string; trackedFields: string[] }[],
|
||||
triggerType: "delete" | "create" | "update"
|
||||
) {
|
||||
const promises = []
|
||||
if (["delete","update"].includes(triggerType)){
|
||||
const promises = [];
|
||||
if (["delete", "update"].includes(triggerType)) {
|
||||
const propagateChangesPromise = propagateChangesOnTrigger(
|
||||
change,
|
||||
triggerType
|
||||
);
|
||||
|
||||
promises.push(propagateChangesPromise)
|
||||
};
|
||||
if(config.length > 0){
|
||||
|
||||
promises.push(propagateChangesPromise);
|
||||
}
|
||||
if (config.length > 0) {
|
||||
if (triggerType === "delete") {
|
||||
config.forEach((c) =>
|
||||
promises.push(removeRefsOnTargetDelete(change.before.ref, c.fieldName))
|
||||
File diff suppressed because it is too large
Load Diff
139
ft_build/index.ts
Normal file
139
ft_build/index.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const cors = require("cors");
|
||||
import { asyncExecute } from "./compiler/terminal";
|
||||
import generateConfig from "./compiler";
|
||||
import { auth } from "./firebaseConfig";
|
||||
import meta from "./package.json";
|
||||
import { commandErrorHandler, logErrorToDB } from "./utils";
|
||||
import firebase from "firebase-admin";
|
||||
|
||||
const app = express();
|
||||
const jsonParser = bodyParser.json();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.get("/", async (req: any, res: any) => {
|
||||
res.send(`Firetable cloud function builder version ${meta.version}`);
|
||||
});
|
||||
|
||||
app.post("/", jsonParser, async (req: any, res: any) => {
|
||||
let user: firebase.auth.UserRecord;
|
||||
|
||||
const userToken = req?.body?.token;
|
||||
if (!userToken) {
|
||||
console.log("missing auth token");
|
||||
res.send({
|
||||
success: false,
|
||||
reason: "missing auth token",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedToken = await auth.verifyIdToken(userToken);
|
||||
const uid = decodedToken.uid;
|
||||
user = await auth.getUser(uid);
|
||||
const roles = user?.customClaims?.roles;
|
||||
if (!roles || !Array.isArray(roles) || !roles?.includes("ADMIN")) {
|
||||
await logErrorToDB({
|
||||
errorDescription: `user is not admin`,
|
||||
user,
|
||||
});
|
||||
res.send({
|
||||
success: false,
|
||||
reason: `user is not admin`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("successfully authenticated");
|
||||
} catch (error) {
|
||||
await logErrorToDB({
|
||||
errorDescription: `error verifying auth token: ${error}`,
|
||||
user,
|
||||
});
|
||||
res.send({
|
||||
success: false,
|
||||
reason: `error verifying auth token: ${error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const configPath = req?.body?.configPath;
|
||||
console.log("configPath:", configPath);
|
||||
|
||||
if (!configPath) {
|
||||
await logErrorToDB({
|
||||
errorDescription: `Invalid configPath (${configPath})`,
|
||||
user,
|
||||
});
|
||||
res.send({
|
||||
success: false,
|
||||
reason: "invalid configPath",
|
||||
});
|
||||
}
|
||||
|
||||
const success = await generateConfig(configPath, user);
|
||||
if (!success) {
|
||||
console.log(`generateConfig failed to complete`);
|
||||
res.send({
|
||||
success: false,
|
||||
reason: `generateConfig failed to complete`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("generateConfig done");
|
||||
|
||||
let hasEnvError = false;
|
||||
if (!process.env._FIREBASE_TOKEN) {
|
||||
await logErrorToDB({
|
||||
errorDescription: `Invalid env: _FIREBASE_TOKEN (${process.env._FIREBASE_TOKEN})`,
|
||||
user,
|
||||
});
|
||||
hasEnvError = true;
|
||||
}
|
||||
|
||||
if (!process.env._PROJECT_ID) {
|
||||
await logErrorToDB({
|
||||
errorDescription: `Invalid env: _PROJECT_ID (${process.env._PROJECT_ID})`,
|
||||
user,
|
||||
});
|
||||
hasEnvError = true;
|
||||
}
|
||||
|
||||
if (hasEnvError) {
|
||||
res.send({
|
||||
success: false,
|
||||
reason: "Invalid env: _FIREBASE_TOKEN or _PROJECT_ID",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await asyncExecute(
|
||||
`cd build/functions; \
|
||||
yarn install`,
|
||||
commandErrorHandler({ user })
|
||||
);
|
||||
|
||||
await asyncExecute(
|
||||
`cd build/functions; \
|
||||
yarn deployFT \
|
||||
--project ${process.env._PROJECT_ID} \
|
||||
--token ${process.env._FIREBASE_TOKEN} \
|
||||
--only functions`,
|
||||
commandErrorHandler({ user })
|
||||
);
|
||||
|
||||
console.log("build complete");
|
||||
res.send({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 8080;
|
||||
app.listen(port, () => {
|
||||
console.log(
|
||||
`Firetable cloud function builder ${meta.version}: listening on port ${port}`
|
||||
);
|
||||
});
|
||||
35
ft_build/package.json
Normal file
35
ft_build/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "ft-functions-builder",
|
||||
"description": "Manages the build and deployment of Firetable cloud functions",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "yarn build && node build",
|
||||
"build": "rm -rf build && tsc --project ./ && cp -r functions build && cp -r sparksLib build",
|
||||
"deploy": "./deploy.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": "14"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"firebase-admin": "^9.2.0",
|
||||
"firebase-functions": "^3.11.0",
|
||||
"safe-eval": "^0.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/node": "^14.14.33",
|
||||
"firebase-tools": "^8.7.0",
|
||||
"husky": "^4.2.5",
|
||||
"js-beautify": "^1.13.0",
|
||||
"prettier": "^2.1.1",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "^4.2.3"
|
||||
}
|
||||
}
|
||||
20
ft_build/tsconfig.json
Normal file
20
ft_build/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"rootDir": "./",
|
||||
"outDir": "./build",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": false,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": false,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["ESNext"],
|
||||
"strictNullChecks": false
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"exclude": ["functions", "build"],
|
||||
"include": ["*.ts", "firebase.json", "sparksLib"]
|
||||
}
|
||||
92
ft_build/utils.ts
Normal file
92
ft_build/utils.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { db } from "./firebaseConfig";
|
||||
import admin from "firebase-admin";
|
||||
const safeEval = require("safe-eval");
|
||||
|
||||
function firetableUser(user: admin.auth.UserRecord) {
|
||||
return {
|
||||
displayName: user?.displayName,
|
||||
email: user?.email,
|
||||
uid: user?.uid,
|
||||
emailVerified: user?.emailVerified,
|
||||
photoURL: user?.photoURL,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
async function insertErrorRecordToDB(errorRecord: object) {
|
||||
await db.collection("_FT_ERRORS").add(errorRecord);
|
||||
}
|
||||
|
||||
function commandErrorHandler(meta: {
|
||||
user: admin.auth.UserRecord;
|
||||
description?: string;
|
||||
functionConfigTs?: string;
|
||||
sparksConfig?: string;
|
||||
}) {
|
||||
return async function (error, stdout, stderr) {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorRecord = {
|
||||
errorType: "commandError",
|
||||
ranBy: firetableUser(meta.user),
|
||||
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
stdout: stdout ?? "",
|
||||
stderr: stderr ?? "",
|
||||
errorStackTrace: error?.stack ?? "",
|
||||
command: error?.cmd ?? "",
|
||||
description: meta?.description ?? "",
|
||||
functionConfigTs: meta?.functionConfigTs ?? "",
|
||||
sparksConfig: meta?.sparksConfig ?? "",
|
||||
};
|
||||
insertErrorRecordToDB(errorRecord);
|
||||
};
|
||||
}
|
||||
|
||||
async function logErrorToDB(data: {
|
||||
errorDescription: string;
|
||||
errorExtraInfo?: string;
|
||||
errorTraceStack?: string;
|
||||
user: admin.auth.UserRecord;
|
||||
sparksConfig?: string;
|
||||
}) {
|
||||
console.error(data.errorDescription);
|
||||
|
||||
const errorRecord = {
|
||||
errorType: "codeError",
|
||||
ranBy: firetableUser(data.user),
|
||||
description: data.errorDescription,
|
||||
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
sparksConfig: data?.sparksConfig ?? "",
|
||||
errorExtraInfo: data?.errorExtraInfo ?? "",
|
||||
errorStackTrace: data?.errorTraceStack ?? "",
|
||||
};
|
||||
|
||||
insertErrorRecordToDB(errorRecord);
|
||||
}
|
||||
|
||||
function parseSparksConfig(
|
||||
sparks: string | undefined,
|
||||
user: admin.auth.UserRecord
|
||||
) {
|
||||
if (sparks) {
|
||||
try {
|
||||
// remove leading "sparks.config(" and trailing ")"
|
||||
return sparks
|
||||
.replace(/^(\s*)sparks.config\(/, "")
|
||||
.replace(/\)(\s*)+$/, "");
|
||||
} catch (error) {
|
||||
logErrorToDB({
|
||||
errorDescription: "Sparks is not wrapped with sparks.config",
|
||||
errorTraceStack: error.stack,
|
||||
user,
|
||||
sparksConfig: sparks,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return "[]";
|
||||
}
|
||||
|
||||
export { commandErrorHandler, logErrorToDB, parseSparksConfig };
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
||||
"@material-ui/lab": "^4.0.0-alpha.56",
|
||||
"@material-ui/pickers": "^3.2.10",
|
||||
"@mdi/js": "^5.8.55",
|
||||
"@monaco-editor/react": "^3.5.5",
|
||||
"@monaco-editor/react": "^4.1.0",
|
||||
"@tinymce/tinymce-react": "^3.4.0",
|
||||
"algoliasearch": "^4.8.6",
|
||||
"chroma-js": "^2.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef, useMemo, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import Editor, { monaco } from "@monaco-editor/react";
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
|
||||
import { useTheme, createStyles, makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
@@ -42,6 +42,7 @@ export default function CodeEditor({
|
||||
const [initialEditorValue] = useState(value ?? "");
|
||||
const { tableState } = useFiretableContext();
|
||||
const classes = useStyles();
|
||||
const monacoInstance = useMonaco();
|
||||
|
||||
const editorRef = useRef<any>();
|
||||
|
||||
@@ -49,39 +50,42 @@ export default function CodeEditor({
|
||||
editorRef.current = editor;
|
||||
}
|
||||
|
||||
function listenEditorChanges() {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.onDidChangeModelContent((ev) => {
|
||||
onChange(editorRef.current.getValue());
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
const themeTransformer = (theme: string) => {
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
return "vs-dark";
|
||||
default:
|
||||
return theme;
|
||||
}
|
||||
};
|
||||
|
||||
useMemo(async () => {
|
||||
monaco
|
||||
.init()
|
||||
.then((monacoInstance) => {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
|
||||
{
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false,
|
||||
}
|
||||
);
|
||||
// compiler options
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
{
|
||||
target: monacoInstance.languages.typescript.ScriptTarget.ES5,
|
||||
allowNonTsExtensions: true,
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) =>
|
||||
console.error(
|
||||
"An error occurred during initialization of Monaco: ",
|
||||
error
|
||||
)
|
||||
if (!monacoInstance) {
|
||||
// useMonaco returns a monaco instance but initialisation is done asynchronously
|
||||
// dont execute the logic until the instance is initialised
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
|
||||
{
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false,
|
||||
}
|
||||
);
|
||||
listenEditorChanges();
|
||||
// compiler options
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
{
|
||||
target: monacoInstance.languages.typescript.ScriptTarget.ES5,
|
||||
allowNonTsExtensions: true,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"An error occurred during initialization of Monaco: ",
|
||||
error
|
||||
);
|
||||
}
|
||||
}, [tableState?.columns]);
|
||||
|
||||
return (
|
||||
@@ -90,9 +94,9 @@ export default function CodeEditor({
|
||||
className={clsx(classes.editorWrapper, wrapperProps?.className)}
|
||||
>
|
||||
<Editor
|
||||
theme={theme.palette.type}
|
||||
theme={themeTransformer(theme.palette.type)}
|
||||
height={height}
|
||||
editorDidMount={handleEditorDidMount}
|
||||
onMount={handleEditorDidMount}
|
||||
language="javascript"
|
||||
value={initialEditorValue}
|
||||
options={{
|
||||
@@ -100,6 +104,7 @@ export default function CodeEditor({
|
||||
fontFamily: theme.typography.fontFamilyMono,
|
||||
...editorOptions,
|
||||
}}
|
||||
onChange={onChange as any}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,23 +5,10 @@ import { FIELDS } from "@antlerengineering/form-builder";
|
||||
import HelperText from "../HelperText";
|
||||
|
||||
export const settings = () => [
|
||||
{ type: FIELDS.heading, label: "Cloud build configuration" },
|
||||
{ type: FIELDS.heading, label: "Cloud Run configuration" },
|
||||
{
|
||||
type: FIELDS.text,
|
||||
name: "cloudBuild.branch",
|
||||
label: "FT Branch",
|
||||
//validation: yup.string().required("Required"),
|
||||
name: "ftBuildUrl",
|
||||
label: "Cloud Run trigger URL",
|
||||
},
|
||||
{
|
||||
type: FIELDS.description,
|
||||
description: (
|
||||
<HelperText>Firetable branch to build cloud functions from</HelperText>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: FIELDS.text,
|
||||
name: "cloudBuild.triggerId",
|
||||
label: "Trigger Id",
|
||||
//validation: yup.string().required("Required"),
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -48,12 +48,13 @@ export default function SettingsDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (!settingsDocState.loading) {
|
||||
const cloudBuild = settingsDocState?.doc?.cloudBuild;
|
||||
setForm(cloudBuild ? { cloudBuild } : FORM_EMPTY_STATE);
|
||||
const ftBuildUrl = settingsDocState?.doc?.ftBuildUrl;
|
||||
setForm({ ftBuildUrl });
|
||||
}
|
||||
}, [settingsDocState.doc, open]);
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
setForm(values)
|
||||
settingsDocDispatch({ action: DocActions.update, data: values });
|
||||
handleClose();
|
||||
};
|
||||
|
||||
@@ -10,7 +10,9 @@ import ErrorBoundary from "components/ErrorBoundary";
|
||||
import Loading from "components/Loading";
|
||||
|
||||
import { useFiretableContext } from "contexts/FiretableContext";
|
||||
import { triggerCloudBuild } from "../../../../firebase/callables";
|
||||
import { useSnackContext } from "contexts/SnackContext";
|
||||
import { db } from "../../../../firebase";
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { useConfirmation } from "components/ConfirmationDialog";
|
||||
import { FieldType } from "constants/fields";
|
||||
|
||||
@@ -31,6 +33,8 @@ export default function FieldSettings(props: IMenuModalProps) {
|
||||
|
||||
const { requestConfirmation } = useConfirmation();
|
||||
const { tableState } = useFiretableContext();
|
||||
const snack = useSnackContext();
|
||||
const appContext = useAppContext();
|
||||
|
||||
const handleChange = (key: string) => (update: any) => {
|
||||
const updatedConfig = _set({ ...newConfig }, key, update);
|
||||
@@ -128,10 +132,36 @@ export default function FieldSettings(props: IMenuModalProps) {
|
||||
confirm: "Deploy",
|
||||
cancel: "Later",
|
||||
handleConfirm: async () => {
|
||||
const response = await triggerCloudBuild(
|
||||
tableState?.config.tableConfig.path
|
||||
);
|
||||
console.log(response);
|
||||
const settingsDoc = await db
|
||||
.doc("/_FIRETABLE_/settings")
|
||||
.get();
|
||||
const ftBuildUrl = settingsDoc.get("ftBuildUrl");
|
||||
if (!ftBuildUrl) {
|
||||
snack.open({
|
||||
message:
|
||||
"Cloud Run trigger URL not configured. Configuration guide: https://github.com/AntlerVC/firetable/wiki/Setting-up-cloud-Run-FT-Builder",
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
|
||||
const userTokenInfo = await appContext?.currentUser?.getIdTokenResult();
|
||||
const userToken = userTokenInfo?.token;
|
||||
try {
|
||||
const response = await fetch(ftBuildUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
configPath: tableState?.config.tableConfig.path,
|
||||
token: userToken,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,30 +4,29 @@ import _get from "lodash/get";
|
||||
import _find from "lodash/find";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
import { useConfirmation } from "components/ConfirmationDialog";
|
||||
import { useSnackContext } from "contexts/SnackContext";
|
||||
import { db } from "../../../firebase";
|
||||
|
||||
import { DialogContentText, Chip } from "@material-ui/core";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
|
||||
import TableHeaderButton from "./TableHeaderButton";
|
||||
import SparkIcon from "@material-ui/icons/OfflineBolt";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import { useFiretableContext } from "contexts/FiretableContext";
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
|
||||
import CodeEditor from "../editors/CodeEditor";
|
||||
|
||||
// import { SnackContext } from "contexts/SnackContext";
|
||||
import { useFiretableContext } from "contexts/FiretableContext";
|
||||
import { triggerCloudBuild } from "firebase/callables";
|
||||
|
||||
export default function SparksEditor() {
|
||||
const snack = useSnackContext();
|
||||
const { tableState, tableActions } = useFiretableContext();
|
||||
|
||||
// const snackContext = useContext(SnackContext);
|
||||
const appContext = useAppContext();
|
||||
const { requestConfirmation } = useConfirmation();
|
||||
|
||||
const currentSparks = tableState?.config.sparks ?? "";
|
||||
const [localSparks, setLocalSparks] = useState(currentSparks);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isSparksValid, setIsSparksValid] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
if (currentSparks !== localSparks) {
|
||||
requestConfirmation({
|
||||
@@ -51,14 +50,38 @@ export default function SparksEditor() {
|
||||
confirm: "Deploy",
|
||||
cancel: "later",
|
||||
handleConfirm: async () => {
|
||||
const response = await triggerCloudBuild(
|
||||
tableState?.config.tableConfig.path
|
||||
);
|
||||
console.log(response);
|
||||
const settingsDoc = await db.doc("/_FIRETABLE_/settings").get();
|
||||
const ftBuildUrl = settingsDoc.get("ftBuildUrl");
|
||||
if (!ftBuildUrl) {
|
||||
snack.open({
|
||||
message:
|
||||
"Cloud Run trigger URL not configured. Configuration guide: https://github.com/AntlerVC/firetable/wiki/Setting-up-cloud-Run-FT-Builder",
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
|
||||
const userTokenInfo = await appContext?.currentUser?.getIdTokenResult();
|
||||
const userToken = userTokenInfo?.token;
|
||||
try {
|
||||
const response = await fetch(ftBuildUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
configPath: tableState?.config.tableConfig.path,
|
||||
token: userToken,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
// const cloudBuild = tableState?.config.tableConfig.doc.cloudBuild;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableHeaderButton
|
||||
@@ -97,14 +120,20 @@ export default function SparksEditor() {
|
||||
handleChange={(newValue) => {
|
||||
setLocalSparks(newValue);
|
||||
}}
|
||||
onValideStatusUpdate={({ isValid }) => {
|
||||
setIsSparksValid(isValid);
|
||||
}}
|
||||
/>
|
||||
{!isSparksValid &&
|
||||
"Please resolve all errors before you are able to save."}
|
||||
</>
|
||||
}
|
||||
actions={{
|
||||
primary: {
|
||||
children: "Save Changes",
|
||||
onClick: handleSave,
|
||||
disabled: localSparks === tableState?.config.sparks,
|
||||
disabled:
|
||||
!isSparksValid || localSparks === tableState?.config.sparks,
|
||||
},
|
||||
secondary: {
|
||||
children: "Cancel",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useRef, useMemo, useState } from "react";
|
||||
import { useTheme, createStyles, makeStyles } from "@material-ui/core/styles";
|
||||
import Editor, { monaco } from "@monaco-editor/react";
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { useFiretableContext } from "contexts/FiretableContext";
|
||||
import { FieldType } from "constants/fields";
|
||||
import { setTimeout } from "timers";
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
@@ -22,8 +21,15 @@ const useStyles = makeStyles((theme) =>
|
||||
);
|
||||
|
||||
export default function CodeEditor(props: any) {
|
||||
const { handleChange, extraLibs, script, height = 400 } = props;
|
||||
const {
|
||||
handleChange,
|
||||
extraLibs,
|
||||
script,
|
||||
height = 400,
|
||||
onValideStatusUpdate,
|
||||
} = props;
|
||||
const theme = useTheme();
|
||||
const monacoInstance = useMonaco();
|
||||
|
||||
const [initialEditorValue] = useState(script ?? "");
|
||||
const { tableState } = useFiretableContext();
|
||||
@@ -35,15 +41,22 @@ export default function CodeEditor(props: any) {
|
||||
editorRef.current = editor;
|
||||
}
|
||||
|
||||
function listenEditorChanges() {
|
||||
setTimeout(() => {
|
||||
editorRef.current?.onDidChangeModelContent((ev) => {
|
||||
handleChange(editorRef.current.getValue());
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
const themeTransformer = (theme: string) => {
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
return "vs-dark";
|
||||
default:
|
||||
return theme;
|
||||
}
|
||||
};
|
||||
|
||||
useMemo(async () => {
|
||||
if (!monacoInstance) {
|
||||
// useMonaco returns a monaco instance but initialisation is done asynchronously
|
||||
// dont execute the logic until the instance is initialised
|
||||
return;
|
||||
}
|
||||
|
||||
const firestoreDefsFile = await fetch(
|
||||
`${process.env.PUBLIC_URL}/firestore.d.ts`
|
||||
);
|
||||
@@ -53,107 +66,301 @@ export default function CodeEditor(props: any) {
|
||||
const firestoreDefs = await firestoreDefsFile.text();
|
||||
// const firebaseAuthDefs = await firebaseAuthDefsFile.text();
|
||||
// console.timeLog(firebaseAuthDefs);
|
||||
monaco
|
||||
.init()
|
||||
.then((monacoInstance) => {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
firestoreDefs
|
||||
);
|
||||
// monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
// firebaseAuthDefs
|
||||
// );
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
|
||||
{
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false,
|
||||
}
|
||||
);
|
||||
// compiler options
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
{
|
||||
target: monacoInstance.languages.typescript.ScriptTarget.ES5,
|
||||
allowNonTsExtensions: true,
|
||||
}
|
||||
);
|
||||
if (extraLibs) {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
extraLibs.join("\n"),
|
||||
"ts:filename/extraLibs.d.ts"
|
||||
);
|
||||
// monaco
|
||||
// .init()
|
||||
// .then((monacoInstance) => {
|
||||
try {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
firestoreDefs
|
||||
);
|
||||
// monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
// firebaseAuthDefs
|
||||
// );
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
|
||||
{
|
||||
noSemanticValidation: false,
|
||||
noSyntaxValidation: true,
|
||||
noSuggestionDiagnostics: true,
|
||||
}
|
||||
);
|
||||
// compiler options
|
||||
monacoInstance.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
{
|
||||
target: monacoInstance.languages.typescript.ScriptTarget.ES2020,
|
||||
allowNonTsExtensions: true,
|
||||
}
|
||||
);
|
||||
if (extraLibs) {
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
[
|
||||
" /**",
|
||||
" * utility functions",
|
||||
" */",
|
||||
"declare namespace utilFns {",
|
||||
" /**",
|
||||
" * Sends out an email through sendGrid",
|
||||
" */",
|
||||
`function sendEmail(msg:{from: string,
|
||||
extraLibs.join("\n"),
|
||||
"ts:filename/extraLibs.d.ts"
|
||||
);
|
||||
}
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
[
|
||||
" /**",
|
||||
" * utility functions",
|
||||
" */",
|
||||
"declare namespace utilFns {",
|
||||
" /**",
|
||||
" * Sends out an email through sendGrid",
|
||||
" */",
|
||||
`function sendEmail(msg:{from: string,
|
||||
templateId:string,
|
||||
personalizations:{to:string,dynamic_template_data:any}[]}):void {
|
||||
|
||||
}`,
|
||||
"}",
|
||||
].join("\n"),
|
||||
"ts:filename/utils.d.ts"
|
||||
);
|
||||
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
[
|
||||
" const db:FirebaseFirestore.Firestore;",
|
||||
// " const auth:admin.auth;",
|
||||
"declare class row {",
|
||||
" /**",
|
||||
" * Returns the row fields",
|
||||
" */",
|
||||
...Object.keys(tableState?.columns!).map((columnKey: string) => {
|
||||
const column = tableState?.columns[columnKey];
|
||||
switch (column.type) {
|
||||
case FieldType.shortText:
|
||||
case FieldType.longText:
|
||||
case FieldType.email:
|
||||
case FieldType.phone:
|
||||
case FieldType.code:
|
||||
return `static ${columnKey}:string`;
|
||||
case FieldType.singleSelect:
|
||||
const typeString = [
|
||||
...column.config.options.map((opt) => `"${opt}"`),
|
||||
// "string",
|
||||
].join(" | ");
|
||||
return `static ${columnKey}:${typeString}`;
|
||||
case FieldType.multiSelect:
|
||||
return `static ${columnKey}:string[]`;
|
||||
case FieldType.checkbox:
|
||||
return `static ${columnKey}:boolean`;
|
||||
default:
|
||||
return `static ${columnKey}:any`;
|
||||
}
|
||||
}),
|
||||
"}",
|
||||
].join("\n"),
|
||||
"ts:filename/rowFields.d.ts"
|
||||
);
|
||||
// monacoInstance.editor.create(wrapper, properties);
|
||||
})
|
||||
.catch((error) =>
|
||||
console.error(
|
||||
"An error occurred during initialization of Monaco: ",
|
||||
error
|
||||
)
|
||||
"}",
|
||||
].join("\n"),
|
||||
"ts:filename/utils.d.ts"
|
||||
);
|
||||
listenEditorChanges();
|
||||
}, [tableState?.columns]);
|
||||
|
||||
const rowDefinition = [
|
||||
...Object.keys(tableState?.columns!).map((columnKey: string) => {
|
||||
const column = tableState?.columns[columnKey];
|
||||
switch (column.type) {
|
||||
case FieldType.shortText:
|
||||
case FieldType.longText:
|
||||
case FieldType.email:
|
||||
case FieldType.phone:
|
||||
case FieldType.code:
|
||||
return `${columnKey}:string`;
|
||||
case FieldType.singleSelect:
|
||||
const typeString = [
|
||||
...(column.config?.options?.map((opt) => `"${opt}"`) ?? []),
|
||||
].join(" | ");
|
||||
return `${columnKey}:${typeString}`;
|
||||
case FieldType.multiSelect:
|
||||
return `${columnKey}:string[]`;
|
||||
case FieldType.checkbox:
|
||||
return `${columnKey}:boolean`;
|
||||
default:
|
||||
return `${columnKey}:any`;
|
||||
}
|
||||
}),
|
||||
].join(";\n");
|
||||
|
||||
const availableFields = Object.keys(tableState?.columns!)
|
||||
.map((columnKey: string) => `"${columnKey}"`)
|
||||
.join("|\n");
|
||||
|
||||
const sparksDefinition = `declare namespace sparks {
|
||||
|
||||
// basic types that are used in all places
|
||||
type Row = {${rowDefinition}};
|
||||
type Field = ${availableFields} | string | object;
|
||||
type Fields = Field[];
|
||||
type Trigger = "create" | "update" | "delete";
|
||||
type Triggers = Trigger[];
|
||||
|
||||
// the argument that the spark body takes in
|
||||
type SparkContext = {
|
||||
row: Row;
|
||||
ref: any;
|
||||
db: any;
|
||||
change: any;
|
||||
triggerType: Triggers;
|
||||
sparkConfig: any;
|
||||
}
|
||||
|
||||
// function types that defines spark body and shuold run
|
||||
type ShouldRun = boolean | ((data: SparkContext) => boolean | Promise<any>);
|
||||
type ContextToString = ((data: SparkContext) => string | Promise<any>);
|
||||
type ContextToStringList = ((data: SparkContext) => string[] | Promise<any>);
|
||||
type ContextToObject = ((data: SparkContext) => object | Promise<any>);
|
||||
type ContextToObjectList = ((data: SparkContext) => object[] | Promise<any>);
|
||||
type ContextToRow = ((data: SparkContext) => Row | Promise<any>);
|
||||
type ContextToAny = ((data: SparkContext) => any | Promise<any>);
|
||||
|
||||
// different types of bodies that slack message can use
|
||||
type slackEmailBody = {
|
||||
channels?: ContextToStringList;
|
||||
text?: ContextToString;
|
||||
emails: ContextToStringList;
|
||||
blocks?: ContextToObjectList;
|
||||
attachments?: ContextToAny;
|
||||
}
|
||||
|
||||
type slackChannelBody = {
|
||||
channels: ContextToStringList;
|
||||
text?: ContextToString;
|
||||
emails?: ContextToStringList;
|
||||
blocks?: ContextToObjectList;
|
||||
attachments?: ContextToAny;
|
||||
}
|
||||
|
||||
// different types of sparks
|
||||
type docSync = {
|
||||
type: "docSync";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: {
|
||||
fieldsToSync: Fields;
|
||||
row: ContextToRow;
|
||||
targetPath: ContextToString;
|
||||
}
|
||||
};
|
||||
|
||||
type historySnapshot = {
|
||||
type: "historySnapshot";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
sparkBody: {
|
||||
trackedFields: Fields;
|
||||
}
|
||||
}
|
||||
|
||||
type algoliaIndex = {
|
||||
type: "algoliaIndex";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: {
|
||||
fieldsToSync: Fields;
|
||||
index: string;
|
||||
row: ContextToRow;
|
||||
objectID: ContextToString;
|
||||
}
|
||||
}
|
||||
|
||||
type slackMessage = {
|
||||
type: "slackMessage";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: slackEmailBody | slackChannelBody;
|
||||
}
|
||||
|
||||
type sendgridEmail = {
|
||||
type: "sendgridEmail";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: {
|
||||
msg: ContextToAny;
|
||||
}
|
||||
}
|
||||
|
||||
type apiCall = {
|
||||
type: "apiCall";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: {
|
||||
body: ContextToString;
|
||||
url: ContextToString;
|
||||
method: ContextToString;
|
||||
callback: ContextToAny;
|
||||
}
|
||||
}
|
||||
|
||||
type twilioMessage = {
|
||||
type: "twilioMessage";
|
||||
triggers: Triggers;
|
||||
shouldRun: ShouldRun;
|
||||
requiredFields?: Fields;
|
||||
sparkBody: {
|
||||
body: ContextToAny;
|
||||
from: ContextToAny;
|
||||
to: ContextToAny;
|
||||
}
|
||||
}
|
||||
|
||||
// an individual spark
|
||||
type Spark =
|
||||
| docSync
|
||||
| historySnapshot
|
||||
| algoliaIndex
|
||||
| slackMessage
|
||||
| sendgridEmail
|
||||
| apiCall
|
||||
| twilioMessage;
|
||||
|
||||
type Sparks = Spark[]
|
||||
|
||||
// use spark.config(sparks) in the code editor for static type check
|
||||
function config(sparks: Sparks): void;
|
||||
}`;
|
||||
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
[
|
||||
" /**",
|
||||
" * sparks type configuration",
|
||||
" */",
|
||||
sparksDefinition,
|
||||
// "interface sparks {",
|
||||
// " /**",
|
||||
// " * define your sparks for current table inside sparks.config",
|
||||
// " */",
|
||||
// `config(spark: object[]):void;`,
|
||||
// "}",
|
||||
].join("\n"),
|
||||
"ts:filename/sparks.d.ts"
|
||||
);
|
||||
|
||||
monacoInstance.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
[
|
||||
" const db:FirebaseFirestore.Firestore;",
|
||||
// " const auth:admin.auth;",
|
||||
"declare class row {",
|
||||
" /**",
|
||||
" * Returns the row fields",
|
||||
" */",
|
||||
...Object.keys(tableState?.columns!).map((columnKey: string) => {
|
||||
const column = tableState?.columns[columnKey];
|
||||
switch (column.type) {
|
||||
case FieldType.shortText:
|
||||
case FieldType.longText:
|
||||
case FieldType.email:
|
||||
case FieldType.phone:
|
||||
case FieldType.code:
|
||||
return `static ${columnKey}:string`;
|
||||
case FieldType.singleSelect:
|
||||
const typeString = [
|
||||
...(column.config?.options?.map((opt) => `"${opt}"`) ?? []),
|
||||
// "string",
|
||||
].join(" | ");
|
||||
return `static ${columnKey}:${typeString}`;
|
||||
case FieldType.multiSelect:
|
||||
return `static ${columnKey}:string[]`;
|
||||
case FieldType.checkbox:
|
||||
return `static ${columnKey}:boolean`;
|
||||
default:
|
||||
return `static ${columnKey}:any`;
|
||||
}
|
||||
}),
|
||||
"}",
|
||||
].join("\n"),
|
||||
"ts:filename/rowFields.d.ts"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"An error occurred during initialization of Monaco: ",
|
||||
error
|
||||
);
|
||||
}
|
||||
}, [tableState?.columns, monacoInstance]);
|
||||
|
||||
function handleEditorValidation(markers) {
|
||||
if (onValideStatusUpdate) {
|
||||
onValideStatusUpdate({
|
||||
isValid: markers.length <= 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.editorWrapper}>
|
||||
<Editor
|
||||
theme={theme.palette.type}
|
||||
theme={themeTransformer(theme.palette.type)}
|
||||
height={height}
|
||||
editorDidMount={handleEditorDidMount}
|
||||
onMount={handleEditorDidMount}
|
||||
language="javascript"
|
||||
value={initialEditorValue}
|
||||
onChange={handleChange}
|
||||
onValidate={handleEditorValidation}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { functions } from "./index";
|
||||
|
||||
export enum CLOUD_FUNCTIONS {
|
||||
ImpersonatorAuth = "callable-ImpersonatorAuth",
|
||||
triggerCloudBuild = "FT_triggerCloudBuild",
|
||||
}
|
||||
|
||||
export const cloudFunction = (
|
||||
@@ -28,6 +27,3 @@ export const cloudFunction = (
|
||||
|
||||
export const ImpersonatorAuth = (email: string) =>
|
||||
functions.httpsCallable(CLOUD_FUNCTIONS.ImpersonatorAuth)({ email });
|
||||
|
||||
export const triggerCloudBuild = (schemaPath: string) =>
|
||||
functions.httpsCallable(CLOUD_FUNCTIONS.triggerCloudBuild)({ schemaPath });
|
||||
|
||||
@@ -1493,13 +1493,6 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.11.0":
|
||||
version "7.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
|
||||
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.8.3":
|
||||
version "7.10.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
|
||||
@@ -1662,10 +1655,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.1.tgz#7815e71c9c6f072034415524b29ca8f1d1770660"
|
||||
integrity sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==
|
||||
|
||||
"@firebase/auth@0.14.9":
|
||||
version "0.14.9"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.14.9.tgz#481db24d5bd6eded8ac2e5aea6edb9307040229c"
|
||||
integrity sha512-PxYa2r5qUEdheXTvqROFrMstK8W4uPiP7NVfp+2Bec+AjY5PxZapCx/YFDLkU0D7YBI82H74PtZrzdJZw7TJ4w==
|
||||
"@firebase/auth@0.15.0":
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.15.0.tgz#45d6def6d6d9444432c005710df442991828275f"
|
||||
integrity sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==
|
||||
dependencies:
|
||||
"@firebase/auth-types" "0.10.1"
|
||||
|
||||
@@ -1697,21 +1690,21 @@
|
||||
faye-websocket "0.11.3"
|
||||
tslib "^1.11.1"
|
||||
|
||||
"@firebase/firestore-types@1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.13.0.tgz#4ab9c40e1e66e8193a929460d64507acd07d9230"
|
||||
integrity sha512-QF5CAuYOHE6Zbsn1uEg6wkl836iP+i6C0C/Zs3kF60eebxZvTWp8JSZk19Ar+jj4w+ye8/7H5olu5CqDNjWpEA==
|
||||
"@firebase/firestore-types@1.14.0":
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.14.0.tgz#4516249d3c181849fd3c856831944dbd5c8c55fc"
|
||||
integrity sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==
|
||||
|
||||
"@firebase/firestore@1.17.3":
|
||||
version "1.17.3"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.17.3.tgz#512d9c1afdba4690aa62de0f53276cf0abbe5f51"
|
||||
integrity sha512-wRdrgeSBJ50eo63x8GnO8NgVNe3vBw2xhKhyMXl0JTWQIbxnlMjAHcz7b85VvsqPLI7U70PgWQnfQtJOXRCNUA==
|
||||
"@firebase/firestore@1.18.0":
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.18.0.tgz#3430e8c60d3e6be1d174b3a258838b1944c93a4d"
|
||||
integrity sha512-maMq4ltkrwjDRusR2nt0qS4wldHQMp+0IDSfXIjC+SNmjnWY/t/+Skn9U3Po+dB38xpz3i7nsKbs+8utpDnPSw==
|
||||
dependencies:
|
||||
"@firebase/component" "0.1.19"
|
||||
"@firebase/firestore-types" "1.13.0"
|
||||
"@firebase/firestore-types" "1.14.0"
|
||||
"@firebase/logger" "0.2.6"
|
||||
"@firebase/util" "0.3.2"
|
||||
"@firebase/webchannel-wrapper" "0.3.0"
|
||||
"@firebase/webchannel-wrapper" "0.4.0"
|
||||
"@grpc/grpc-js" "^1.0.0"
|
||||
"@grpc/proto-loader" "^0.5.0"
|
||||
node-fetch "2.6.1"
|
||||
@@ -1836,10 +1829,10 @@
|
||||
dependencies:
|
||||
tslib "^1.11.1"
|
||||
|
||||
"@firebase/webchannel-wrapper@0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.3.0.tgz#d1689566b94c25423d1fb2cb031c5c2ea4c9f939"
|
||||
integrity sha512-VniCGPIgSGNEgOkh5phb3iKmSGIzcwrccy3IomMFRWPCMiCk2y98UQNJEoDs1yIHtZMstVjYWKYxnunIGzC5UQ==
|
||||
"@firebase/webchannel-wrapper@0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz#becce788818d3f47f0ac1a74c3c061ac1dcf4f6d"
|
||||
integrity sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==
|
||||
|
||||
"@google-cloud/paginator@^2.0.0":
|
||||
version "2.0.3"
|
||||
@@ -2224,12 +2217,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-5.8.55.tgz#630bc5fafd8b1d2f6e63489a9ab170177559e41b"
|
||||
integrity sha512-2bvln56SW6V/nSDC/0/NTu1bMF/CgSyZox8mcWbAPWElBN3UYIrukKDUckEER8ifr8X2YJl1RLKQqi7T7qLzmg==
|
||||
|
||||
"@monaco-editor/react@^3.5.5":
|
||||
version "3.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-3.5.5.tgz#7ebef1903bf7e7925084f16efb6550a83a7efed6"
|
||||
integrity sha512-2/j9KFMu0Lr1DKX6ZqPwiCbhfv47yD8849Cyi8BTlGGRIzCcrF2pIQPbsZrsO+A+uG/352BfOsSF3zKQT84NQQ==
|
||||
"@monaco-editor/loader@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.0.1.tgz#7068c9b07bbc65387c0e7a4df6dac0a326155905"
|
||||
integrity sha512-hycGOhLqLYjnD0A/FHs56covEQWnDFrSnm/qLKkB/yoeayQ7ju+Vaj4SdTojGrXeY6jhMDx59map0+Jqwquh1Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.0"
|
||||
state-local "^1.0.6"
|
||||
|
||||
"@monaco-editor/react@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.1.0.tgz#9a362a4e0973c9958574551984a3c3eeeff06e5f"
|
||||
integrity sha512-Hh895v/KfGgckDLXq8sdDGT4xS89+2hbQOP1l57sLd2XlJycChdzPiCj02nQDIduLmUIVHittjaj1/xmy94C3A==
|
||||
dependencies:
|
||||
"@monaco-editor/loader" "^1.0.1"
|
||||
prop-types "^15.7.2"
|
||||
state-local "^1.0.7"
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
@@ -6761,16 +6763,16 @@ firebase-tools@^8.12.1:
|
||||
ws "^7.2.3"
|
||||
|
||||
firebase@^7.23.0:
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.23.0.tgz#d58bf936bd9f3a00717d81854280747222d2803b"
|
||||
integrity sha512-0b1zi0H8jT4KqyPabldzPhyKTeptw5E5a7KkjWW3MBMVV/LjbC6/NKhRR8sGQNbsbS2LnTvyEENWbqkZP2ZXtw==
|
||||
version "7.24.0"
|
||||
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.24.0.tgz#dab53b9c0f1c9538d2d6f4f51769897b0b6d60d8"
|
||||
integrity sha512-j6jIyGFFBlwWAmrlUg9HyQ/x+YpsPkc/TTkbTyeLwwAJrpAmmEHNPT6O9xtAnMV4g7d3RqLL/u9//aZlbY4rQA==
|
||||
dependencies:
|
||||
"@firebase/analytics" "0.6.0"
|
||||
"@firebase/app" "0.6.11"
|
||||
"@firebase/app-types" "0.6.1"
|
||||
"@firebase/auth" "0.14.9"
|
||||
"@firebase/auth" "0.15.0"
|
||||
"@firebase/database" "0.6.13"
|
||||
"@firebase/firestore" "1.17.3"
|
||||
"@firebase/firestore" "1.18.0"
|
||||
"@firebase/functions" "0.5.1"
|
||||
"@firebase/installations" "0.4.17"
|
||||
"@firebase/messaging" "0.7.1"
|
||||
@@ -13804,6 +13806,11 @@ stack-utils@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
|
||||
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
|
||||
|
||||
state-local@^1.0.6, state-local@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5"
|
||||
integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
|
||||
Reference in New Issue
Block a user