@arcforgelabs/flow 2026.10.33 → 2026.10.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server/api/custom-fields.js +114 -0
- package/server/api/invoicing.js +2 -1
- package/server/api/work-moves.js +80 -0
- package/server/app.js +12 -0
- package/server/core/custom-fields.js +300 -0
- package/server/core/job-board.js +2 -0
- package/server/core/tracing.js +64 -0
- package/server/db/migrations/0018_custom_fields.sql +78 -0
- package/server/db/migrations/meta/0018_snapshot.json +4289 -0
- package/server/db/migrations/meta/_journal.json +7 -0
- package/server/db/schema.js +59 -0
- package/server/index.js +6 -1
- package/server/integrations/port.js +7 -1
- package/server/integrations/seazona/index.js +47 -1
- package/server/integrations/seazona/migrations/0007_stage_moves.sql +18 -0
- package/server/integrations/seazona/migrations/meta/0007_snapshot.json +1519 -0
- package/server/integrations/seazona/migrations/meta/_journal.json +7 -0
- package/server/integrations/seazona/schema.js +26 -0
- package/server/integrations/seazona/settings.js +14 -0
- package/server/integrations/seazona/stage-moves.js +135 -0
- package/server/integrations/seazona/tables.js +2 -0
- package/server/integrations/sentry/index.js +61 -10
- package/server/ui/app-page.js +1 -1
- package/server/ui/app.js +40 -2
- package/server/ui/custom-fields.js +190 -0
- package/server/ui/flow.css +48 -9
- package/server/ui/invoicing.js +83 -5
- package/server/ui/jobs.js +3 -1
- package/server/ui/monitoring.js +44 -2
- package/server/ui/routes.js +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { routePattern } from '../ui/monitoring.js';
|
|
2
|
+
|
|
3
|
+
// Performance traces for the installation's own tracing service
|
|
4
|
+
// (docs/SENTRY.md, "Traces"). The first integration that is on and offers the
|
|
5
|
+
// `traceSpan` and `traceRequest` capabilities gets them. With none on, each
|
|
6
|
+
// function here runs the work as it is and adds nothing.
|
|
7
|
+
//
|
|
8
|
+
// `tracers()` answers the adapters to trace with; it is read at each call,
|
|
9
|
+
// so an integration turned on or off later is followed.
|
|
10
|
+
export function createTracing({ tracers = () => [] } = {}) {
|
|
11
|
+
const first = () => tracers()[0]?.capabilities ?? null;
|
|
12
|
+
return {
|
|
13
|
+
// Runs `work` inside a span named `name`, of kind `op`.
|
|
14
|
+
span(name, op, work, attributes = {}) {
|
|
15
|
+
const tracer = first();
|
|
16
|
+
return tracer ? tracer.traceSpan({ name, op, attributes }, work) : work();
|
|
17
|
+
},
|
|
18
|
+
// Express middleware: names each request's trace by its route pattern
|
|
19
|
+
// ("GET /api/invoicing/invoices/:id"), never by its URL, so the same
|
|
20
|
+
// click on another invoice is the same name.
|
|
21
|
+
requests() {
|
|
22
|
+
return (req, res, next) => {
|
|
23
|
+
const rename = first()?.traceRequest() ?? null;
|
|
24
|
+
if (rename) res.once('finish', () => rename(requestName(req)));
|
|
25
|
+
next();
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The route Express matched, else the path with its ids as ":id".
|
|
32
|
+
export function requestName(req) {
|
|
33
|
+
const route = typeof req.route?.path === 'string' ? `${req.baseUrl ?? ''}${req.route.path}` : null;
|
|
34
|
+
return `${req.method} ${route ?? routePattern(req.originalUrl ?? req.url)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A span for each database query the pool runs. The span's name is the SQL
|
|
38
|
+
// with its values left out: Flow passes values as parameters, and any quoted
|
|
39
|
+
// text or number in the SQL itself becomes "?".
|
|
40
|
+
export function traceQueries(pool, tracing) {
|
|
41
|
+
const wrap = (target) => {
|
|
42
|
+
const query = target.query.bind(target);
|
|
43
|
+
target.query = (...args) => {
|
|
44
|
+
// Callback calls are pg's own (the pool's query runs through a
|
|
45
|
+
// client's): the promise call around them already has its span.
|
|
46
|
+
if (typeof args.at(-1) === 'function') return query(...args);
|
|
47
|
+
return tracing.span(sqlName(args[0]), 'db', () => query(...args), { 'db.system': 'postgresql', 'db.system.name': 'postgresql' });
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
wrap(pool);
|
|
51
|
+
// A client a transaction holds (pool.connect()) queries directly.
|
|
52
|
+
pool.on('connect', wrap);
|
|
53
|
+
return pool;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function sqlName(statement) {
|
|
57
|
+
const text = typeof statement === 'string' ? statement : String(statement?.text ?? '');
|
|
58
|
+
return text
|
|
59
|
+
.replace(/'(?:[^']|'')*'/g, '?')
|
|
60
|
+
.replace(/(?<![\w$])\d+(\.\d+)?\b/g, '?')
|
|
61
|
+
.replace(/\s+/g, ' ')
|
|
62
|
+
.trim()
|
|
63
|
+
.slice(0, 300) || 'query';
|
|
64
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "job_field_events" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"organisation_id" uuid NOT NULL,
|
|
4
|
+
"field_id" uuid NOT NULL,
|
|
5
|
+
"source_system" text NOT NULL,
|
|
6
|
+
"source_job_ref" text NOT NULL,
|
|
7
|
+
"from_value" jsonb,
|
|
8
|
+
"to_value" jsonb,
|
|
9
|
+
"actor" uuid,
|
|
10
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
11
|
+
);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE TABLE IF NOT EXISTS "job_field_values" (
|
|
14
|
+
"organisation_id" uuid NOT NULL,
|
|
15
|
+
"field_id" uuid NOT NULL,
|
|
16
|
+
"source_system" text NOT NULL,
|
|
17
|
+
"source_job_ref" text NOT NULL,
|
|
18
|
+
"value" jsonb NOT NULL,
|
|
19
|
+
"updated_by" uuid,
|
|
20
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
21
|
+
CONSTRAINT "job_field_values_organisation_id_field_id_source_system_source_job_ref_pk" PRIMARY KEY("organisation_id","field_id","source_system","source_job_ref")
|
|
22
|
+
);
|
|
23
|
+
--> statement-breakpoint
|
|
24
|
+
CREATE TABLE IF NOT EXISTS "job_fields" (
|
|
25
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
26
|
+
"organisation_id" uuid NOT NULL,
|
|
27
|
+
"name" text NOT NULL,
|
|
28
|
+
"type" text NOT NULL,
|
|
29
|
+
"options" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
30
|
+
"position" integer NOT NULL,
|
|
31
|
+
"archived_at" timestamp with time zone,
|
|
32
|
+
"created_by" uuid,
|
|
33
|
+
"updated_by" uuid,
|
|
34
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
35
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
36
|
+
CONSTRAINT "job_fields_org_id" UNIQUE("organisation_id","id"),
|
|
37
|
+
CONSTRAINT "job_fields_type_valid" CHECK ("job_fields"."type" in ('dropdown', 'person', 'checkbox', 'date', 'number', 'text')),
|
|
38
|
+
CONSTRAINT "job_fields_name_valid" CHECK (length("job_fields"."name") between 1 and 60)
|
|
39
|
+
);
|
|
40
|
+
--> statement-breakpoint
|
|
41
|
+
DO $flow_guard$ BEGIN
|
|
42
|
+
ALTER TABLE "job_field_events" ADD CONSTRAINT "job_field_events_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;
|
|
43
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
44
|
+
END $flow_guard$;--> statement-breakpoint
|
|
45
|
+
DO $flow_guard$ BEGIN
|
|
46
|
+
ALTER TABLE "job_field_events" ADD CONSTRAINT "job_field_events_actor_people_id_fk" FOREIGN KEY ("actor") REFERENCES "public"."people"("id") ON DELETE set null ON UPDATE no action;
|
|
47
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
48
|
+
END $flow_guard$;--> statement-breakpoint
|
|
49
|
+
DO $flow_guard$ BEGIN
|
|
50
|
+
ALTER TABLE "job_field_events" ADD CONSTRAINT "job_field_events_field_same_org" FOREIGN KEY ("organisation_id","field_id") REFERENCES "public"."job_fields"("organisation_id","id") ON DELETE cascade ON UPDATE no action;
|
|
51
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
52
|
+
END $flow_guard$;--> statement-breakpoint
|
|
53
|
+
DO $flow_guard$ BEGIN
|
|
54
|
+
ALTER TABLE "job_field_values" ADD CONSTRAINT "job_field_values_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;
|
|
55
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
56
|
+
END $flow_guard$;--> statement-breakpoint
|
|
57
|
+
DO $flow_guard$ BEGIN
|
|
58
|
+
ALTER TABLE "job_field_values" ADD CONSTRAINT "job_field_values_updated_by_people_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."people"("id") ON DELETE set null ON UPDATE no action;
|
|
59
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
60
|
+
END $flow_guard$;--> statement-breakpoint
|
|
61
|
+
DO $flow_guard$ BEGIN
|
|
62
|
+
ALTER TABLE "job_field_values" ADD CONSTRAINT "job_field_values_field_same_org" FOREIGN KEY ("organisation_id","field_id") REFERENCES "public"."job_fields"("organisation_id","id") ON DELETE cascade ON UPDATE no action;
|
|
63
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
64
|
+
END $flow_guard$;--> statement-breakpoint
|
|
65
|
+
DO $flow_guard$ BEGIN
|
|
66
|
+
ALTER TABLE "job_fields" ADD CONSTRAINT "job_fields_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;
|
|
67
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
68
|
+
END $flow_guard$;--> statement-breakpoint
|
|
69
|
+
DO $flow_guard$ BEGIN
|
|
70
|
+
ALTER TABLE "job_fields" ADD CONSTRAINT "job_fields_created_by_people_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."people"("id") ON DELETE set null ON UPDATE no action;
|
|
71
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
72
|
+
END $flow_guard$;--> statement-breakpoint
|
|
73
|
+
DO $flow_guard$ BEGIN
|
|
74
|
+
ALTER TABLE "job_fields" ADD CONSTRAINT "job_fields_updated_by_people_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."people"("id") ON DELETE set null ON UPDATE no action;
|
|
75
|
+
EXCEPTION WHEN duplicate_object OR duplicate_table OR invalid_table_definition THEN NULL;
|
|
76
|
+
END $flow_guard$;--> statement-breakpoint
|
|
77
|
+
CREATE INDEX IF NOT EXISTS "job_field_events_job" ON "job_field_events" USING btree ("organisation_id","source_system","source_job_ref","created_at");--> statement-breakpoint
|
|
78
|
+
CREATE INDEX IF NOT EXISTS "job_field_values_job" ON "job_field_values" USING btree ("organisation_id","source_system","source_job_ref");
|