initial commit
This commit is contained in:
commit
89b15af73e
12 changed files with 479 additions and 0 deletions
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Project repo_PLTo23
|
||||||
5
payment-display-p_WxCpgo3/.workflow.state
Normal file
5
payment-display-p_WxCpgo3/.workflow.state
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# DO NOT EDIT THIS FILE
|
||||||
|
---
|
||||||
|
schema: workflow.state/2022.04
|
||||||
|
id: p_WxCpgo3
|
||||||
|
|
||||||
34
payment-display-p_WxCpgo3/calculate/entry.js
Normal file
34
payment-display-p_WxCpgo3/calculate/entry.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
export default defineComponent({
|
||||||
|
async run({ steps }) {
|
||||||
|
const rows = steps.parse.$return_value.rows;
|
||||||
|
|
||||||
|
if (!rows || !rows.length) {
|
||||||
|
throw new Error("No parsed rows found from the parse step.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseAmount = (value) => {
|
||||||
|
const num = parseFloat(String(value || "0").replace(/,/g, "").trim());
|
||||||
|
return isNaN(num) ? 0 : num;
|
||||||
|
};
|
||||||
|
|
||||||
|
const payments = rows.map((row) => ({
|
||||||
|
method: (row["Method"] || "").trim(),
|
||||||
|
payee: (row["Payee"] || "").trim(),
|
||||||
|
invoice_numbers: (row["Invoice Numbers"] || "").trim(),
|
||||||
|
send_payment_on: (row["Send Payment On"] || "").trim(),
|
||||||
|
amount_usd: parseAmount(row["Amount in USD"]),
|
||||||
|
status: (row["Status"] || "").trim(),
|
||||||
|
company_subsidiary: (row["Company/Subsidiary"] || "").trim(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const grand_total = payments.reduce((sum, p) => sum + p.amount_usd, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
file_name: steps.parse.$return_value.file_name,
|
||||||
|
row_count: payments.length,
|
||||||
|
grand_count: payments.length,
|
||||||
|
grand_total: Number(grand_total.toFixed(2)),
|
||||||
|
payments,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
61
payment-display-p_WxCpgo3/filter/entry.js
Normal file
61
payment-display-p_WxCpgo3/filter/entry.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
export default {
|
||||||
|
type: "action",
|
||||||
|
props: {
|
||||||
|
_filter: "any",
|
||||||
|
},
|
||||||
|
run({ $ }) {
|
||||||
|
const _filter = this._filter
|
||||||
|
if (!_filter || typeof _filter !== "object") {
|
||||||
|
throw new Error("filter not configured correctly")
|
||||||
|
}
|
||||||
|
const messageOnContinue = "Continuing workflow"
|
||||||
|
const messageOnEnd = "Ending workflow"
|
||||||
|
let result = evalRuleGroup(_filter.rules)
|
||||||
|
const _continue = ((typeof _filter.continue === "undefined") || _filter.continue === "true") ? result : !result
|
||||||
|
return _continue ? $.export("$summary", messageOnContinue) : $.flow.exit(messageOnEnd)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// config.rules
|
||||||
|
function evalRuleGroup(rules) {
|
||||||
|
const and = rules.t === "and"
|
||||||
|
let v = and
|
||||||
|
for (const r of rules.r) {
|
||||||
|
const b = ("t" in r) ? evalRuleGroup(r) : evalRule(r)
|
||||||
|
v = and ? v && b : v || b
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
// XXX if rule.a is {{something}} should probably NOT +rule.a, but if rule.a is "12" we should... hmm
|
||||||
|
function evalRule(rule) {
|
||||||
|
switch (rule.op) {
|
||||||
|
default: throw new Error(`unhandled op: ${rule.op}`)
|
||||||
|
case "exist": return rule.a !== undefined
|
||||||
|
case "!exist": return rule.a === undefined
|
||||||
|
case "null": return rule.a === null
|
||||||
|
case "!null": return rule.a !== null
|
||||||
|
case "str": return typeof rule.a === "string"
|
||||||
|
case "!str": return typeof rule.a !== "string"
|
||||||
|
case "str ": return typeof rule.a === "string" && !!rule.a.match(/^\s*$/)
|
||||||
|
case "str! ": return typeof rule.a === "string" && !rule.a.match(/^\s*$/)
|
||||||
|
case "str=": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.a.localeCompare(rule.b, undefined, { sensitivity: rule.matchCase ? "variant" : "base" }) == 0)
|
||||||
|
case "str!=": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.a.localeCompare(rule.b, undefined, { sensitivity: rule.matchCase ? "variant" : "base" }) != 0)
|
||||||
|
case "str^": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.matchCase ? rule.a.startsWith(rule.b) : rule.a.toLowerCase().startsWith(rule.b.toLowerCase()))
|
||||||
|
case "str/": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.matchCase ? rule.a.includes(rule.b) : rule.a.toLowerCase().includes(rule.b.toLowerCase()))
|
||||||
|
case "str!/": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.matchCase ? !rule.a.includes(rule.b) : !rule.a.toLowerCase().includes(rule.b.toLowerCase()))
|
||||||
|
case "str$": return typeof rule.a === "string" && typeof rule.b === "string" && (rule.matchCase ? rule.a.endsWith(rule.b) : rule.a.toLowerCase().endsWith(rule.b.toLowerCase()))
|
||||||
|
case "num": return typeof rule.a === "number"
|
||||||
|
case "!num": return typeof rule.a !== "number"
|
||||||
|
case "num=": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a === +rule.b
|
||||||
|
case "num!=": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a !== +rule.b
|
||||||
|
case "num>": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a > +rule.b
|
||||||
|
case "num>=": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a >= +rule.b
|
||||||
|
case "num<": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a < +rule.b
|
||||||
|
case "num<=": return typeof +rule.a === "number" && typeof +rule.b === "number" && +rule.a <= +rule.b
|
||||||
|
case "true": return typeof rule.a === "boolean" && rule.a
|
||||||
|
case "false": return typeof rule.a === "boolean" && !rule.a
|
||||||
|
case "bool": return typeof rule.a === "boolean"
|
||||||
|
case "!bool": return typeof rule.a !== "boolean"
|
||||||
|
case "bool=": return typeof rule.a === "boolean" && typeof rule.b === "boolean" && rule.a === rule.b
|
||||||
|
}
|
||||||
|
}
|
||||||
37
payment-display-p_WxCpgo3/parse/entry.js
Normal file
37
payment-display-p_WxCpgo3/parse/entry.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import { parse } from "csv-parse/sync";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
async run({ steps }) {
|
||||||
|
const filePath = `/tmp/${steps.trigger.event.name}`;
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`File not found at ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvText = fs.readFileSync(filePath, "utf8");
|
||||||
|
|
||||||
|
const rows = parse(csvText, {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true,
|
||||||
|
trim: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedRows = rows.map((row) => {
|
||||||
|
const clean = {};
|
||||||
|
for (const [key, value] of Object.entries(row)) {
|
||||||
|
clean[String(key).trim()] = typeof value === "string" ? value.trim() : value;
|
||||||
|
}
|
||||||
|
return clean;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
file_path: filePath,
|
||||||
|
file_name: steps.trigger.event.name,
|
||||||
|
row_count: normalizedRows.length,
|
||||||
|
headers: normalizedRows.length ? Object.keys(normalizedRows[0]) : [],
|
||||||
|
sample_rows: normalizedRows.slice(0, 5),
|
||||||
|
rows: normalizedRows,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
8
payment-display-p_WxCpgo3/prepare_dashboard/entry.js
Normal file
8
payment-display-p_WxCpgo3/prepare_dashboard/entry.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default defineComponent({
|
||||||
|
async run({ steps }) {
|
||||||
|
return {
|
||||||
|
...steps.calculate.$return_value,
|
||||||
|
last_updated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
49
payment-display-p_WxCpgo3/workflow.yaml
Normal file
49
payment-display-p_WxCpgo3/workflow.yaml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
schema: workflow/2022.04
|
||||||
|
name: Payment Display
|
||||||
|
settings:
|
||||||
|
error_notification: true
|
||||||
|
triggers:
|
||||||
|
- id: dc_QyuaX1X
|
||||||
|
steps:
|
||||||
|
- namespace: filter
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./filter/entry.js"
|
||||||
|
props:
|
||||||
|
_filter:
|
||||||
|
continue: 'true'
|
||||||
|
rules:
|
||||||
|
t: and
|
||||||
|
r:
|
||||||
|
- a: "{{steps.trigger.event.fileExtension}}"
|
||||||
|
op: str=
|
||||||
|
b: "{{steps.trigger.event.fileExtension}}"
|
||||||
|
matchCase: true
|
||||||
|
- namespace: download_file
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: google_drive-download-file@0.1.22
|
||||||
|
props:
|
||||||
|
googleDrive:
|
||||||
|
authProvisionId: apn_dbh7Jkb
|
||||||
|
drive: My Drive
|
||||||
|
fileId: "{{steps.trigger.event.id}}"
|
||||||
|
filePath: "/tmp/{{steps.trigger.event.name}}"
|
||||||
|
- namespace: parse
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./parse/entry.js"
|
||||||
|
- namespace: calculate
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./calculate/entry.js"
|
||||||
|
- namespace: prepare_dashboard
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./prepare_dashboard/entry.js"
|
||||||
|
- namespace: save_to_datastore
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: data_stores-add-update-multiple-records@0.0.9
|
||||||
|
props:
|
||||||
|
dataStore:
|
||||||
|
storeId: ds_5ludZgA
|
||||||
|
data:
|
||||||
|
current_dashboard: "{{steps.prepare_dashboard.$return_value}}"
|
||||||
|
? "\n"
|
||||||
|
: ''
|
||||||
|
|
||||||
5
slack-app-home-p_3nC3laY/.workflow.state
Normal file
5
slack-app-home-p_3nC3laY/.workflow.state
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# DO NOT EDIT THIS FILE
|
||||||
|
---
|
||||||
|
schema: workflow.state/2022.04
|
||||||
|
id: p_3nC3laY
|
||||||
|
|
||||||
182
slack-app-home-p_3nC3laY/build_home_view/entry.js
Normal file
182
slack-app-home-p_3nC3laY/build_home_view/entry.js
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
export default defineComponent({
|
||||||
|
async run({ steps }) {
|
||||||
|
const dashboard = steps.get_current_dashboard.$return_value;
|
||||||
|
const slackEvent = steps.handle_slack_event.$return_value || {};
|
||||||
|
|
||||||
|
const userId =
|
||||||
|
slackEvent.user_id ||
|
||||||
|
slackEvent.raw_body?.event?.user ||
|
||||||
|
slackEvent.raw_body?.user ||
|
||||||
|
slackEvent.raw_body?.authorizations?.[0]?.user_id;
|
||||||
|
|
||||||
|
if (!dashboard || Object.keys(dashboard).length === 0) {
|
||||||
|
throw new Error("No dashboard data found in Data Store.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error("No Slack user ID found in the event payload.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payments = Array.isArray(dashboard.payments) ? dashboard.payments : [];
|
||||||
|
|
||||||
|
const formatCurrency = (value) =>
|
||||||
|
new Intl.NumberFormat("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD",
|
||||||
|
}).format(Number(value || 0));
|
||||||
|
|
||||||
|
const parseMDYLocal = (value) => {
|
||||||
|
if (!value) return null;
|
||||||
|
const parts = String(value).split("/");
|
||||||
|
if (parts.length !== 3) return null;
|
||||||
|
|
||||||
|
const [mm, dd, yyyy] = parts.map((p) => parseInt(p, 10));
|
||||||
|
if (!mm || !dd || !yyyy) return null;
|
||||||
|
|
||||||
|
return new Date(yyyy, mm - 1, dd);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateKey = (date) => {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDisplayDate = (dateKey) => {
|
||||||
|
const [y, m, d] = dateKey.split("-").map(Number);
|
||||||
|
const dt = new Date(y, m - 1, d);
|
||||||
|
return dt.toLocaleDateString("en-US", {
|
||||||
|
weekday: "short",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const futurePayments = payments.filter((p) => {
|
||||||
|
const dt = parseMDYLocal(p.send_payment_on);
|
||||||
|
return dt && dt >= today && (p.method === "ACH" || p.method === "Check");
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupByMethodAndDay = (methodName) => {
|
||||||
|
const grouped = {};
|
||||||
|
|
||||||
|
for (const payment of futurePayments) {
|
||||||
|
if (payment.method !== methodName) continue;
|
||||||
|
|
||||||
|
const dt = parseMDYLocal(payment.send_payment_on);
|
||||||
|
if (!dt) continue;
|
||||||
|
|
||||||
|
const key = formatDateKey(dt);
|
||||||
|
|
||||||
|
if (!grouped[key]) {
|
||||||
|
grouped[key] = {
|
||||||
|
count: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped[key].count += 1;
|
||||||
|
grouped[key].total += payment.amount_usd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(grouped)
|
||||||
|
.sort()
|
||||||
|
.map((dateKey) => ({
|
||||||
|
date_key: dateKey,
|
||||||
|
display_date: formatDisplayDate(dateKey),
|
||||||
|
count: grouped[dateKey].count,
|
||||||
|
total: Number(grouped[dateKey].total.toFixed(2)),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const achDays = groupByMethodAndDay("ACH");
|
||||||
|
const checkDays = groupByMethodAndDay("Check");
|
||||||
|
|
||||||
|
const buildDayBlocks = (title, days) => {
|
||||||
|
const total = days.reduce((sum, d) => sum + d.total, 0);
|
||||||
|
const count = days.reduce((sum, d) => sum + d.count, 0);
|
||||||
|
|
||||||
|
const blocks = [
|
||||||
|
{
|
||||||
|
type: "section",
|
||||||
|
text: {
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `*${title}*\n${count} payments • ${formatCurrency(total)}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "divider",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!days.length) {
|
||||||
|
blocks.push({
|
||||||
|
type: "section",
|
||||||
|
text: {
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: "_No upcoming payments found._",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
blocks.push({ type: "divider" });
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const day of days) {
|
||||||
|
blocks.push({
|
||||||
|
type: "section",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `*${day.display_date}*`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `${day.count} payments • ${formatCurrency(day.total)}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push({ type: "divider" });
|
||||||
|
return blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
user_id: userId,
|
||||||
|
view: {
|
||||||
|
type: "home",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: "header",
|
||||||
|
text: {
|
||||||
|
type: "plain_text",
|
||||||
|
text: "Payments Dashboard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "context",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `*Last updated:* ${dashboard.last_updated || "N/A"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `*Source:* ${dashboard.file_name || "N/A"}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "divider",
|
||||||
|
},
|
||||||
|
...buildDayBlocks("ACH", achDays),
|
||||||
|
...buildDayBlocks("Check", checkDays),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
32
slack-app-home-p_3nC3laY/handle_slack_event/entry.js
Normal file
32
slack-app-home-p_3nC3laY/handle_slack_event/entry.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export default defineComponent({
|
||||||
|
async run({ steps, $ }) {
|
||||||
|
const body = steps.trigger.event.body || steps.trigger.event;
|
||||||
|
|
||||||
|
if (body.type === "url_verification") {
|
||||||
|
await $.respond({
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: body.challenge,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { verified: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
await $.respond({
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: { ok: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
event_type: body.event?.type,
|
||||||
|
user_id: body.event?.user,
|
||||||
|
tab: body.event?.tab,
|
||||||
|
raw_body: body,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
34
slack-app-home-p_3nC3laY/publish_home_view/entry.js
Normal file
34
slack-app-home-p_3nC3laY/publish_home_view/entry.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
slack_bot_token: {
|
||||||
|
type: "string",
|
||||||
|
label: "Slack Bot Token",
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ steps }) {
|
||||||
|
const user_id = steps.build_home_view.$return_value.user_id;
|
||||||
|
const view = steps.build_home_view.$return_value.view;
|
||||||
|
|
||||||
|
const res = await fetch("https://slack.com/api/views.publish", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${this.slack_bot_token}`,
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user_id,
|
||||||
|
view,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!data.ok) {
|
||||||
|
throw new Error(`Slack API error: ${JSON.stringify(data)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
31
slack-app-home-p_3nC3laY/workflow.yaml
Normal file
31
slack-app-home-p_3nC3laY/workflow.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
schema: workflow/2022.04
|
||||||
|
name: Slack App Home
|
||||||
|
settings:
|
||||||
|
error_notification: true
|
||||||
|
triggers:
|
||||||
|
- id: hi_JYH1xQr
|
||||||
|
steps:
|
||||||
|
- namespace: handle_slack_event
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./handle_slack_event/entry.js"
|
||||||
|
- namespace: get_current_dashboard
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: data_stores-get-record-or-create@0.0.15
|
||||||
|
_props_cursor: dyp_97UDZ5q7
|
||||||
|
props:
|
||||||
|
dataStore:
|
||||||
|
storeId: ds_5ludZgA
|
||||||
|
key:
|
||||||
|
__lv:
|
||||||
|
label: current_dashboard
|
||||||
|
value: current_dashboard
|
||||||
|
- namespace: build_home_view
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./build_home_view/entry.js"
|
||||||
|
- namespace: publish_home_view
|
||||||
|
runtime: nodejs20.x
|
||||||
|
uses: "./publish_home_view/entry.js"
|
||||||
|
props:
|
||||||
|
slack_bot_token:
|
||||||
|
_enc: 3334db428df05327a883590347d39b440694f03a89b06503e73654e3596a52ab$$3g0UG9rIhQO0+wd9MbTJY9JBjSR6VpcXuem3drNL+PxsmBJzTVmt5avHP3RBbzs5m5t+mbJONef+qX1M2qvlfQZdpK9N--EzC2Qq8RMT6RKnjw--XsAu4D7SGePTz/THhitNIQ==
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue