mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat(core): Add workflow review activity log tables and migration (no-changelog) (#35517)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Sandra Zollner <sandra.zollner@n8n.io> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d7dcba5c80
commit
41d54e77f1
|
|
@ -126,10 +126,12 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
|
|||
| [public.workflow_publication_trigger_status](public.workflow_publication_trigger_status.md) | 8 | | BASE TABLE |
|
||||
| [public.workflow_publish_history](public.workflow_publish_history.md) | 6 | | BASE TABLE |
|
||||
| [public.workflow_published_version](public.workflow_published_version.md) | 4 | | BASE TABLE |
|
||||
| [public.workflow_review_activity](public.workflow_review_activity.md) | 8 | | BASE TABLE |
|
||||
| [public.workflow_review_activity_comment](public.workflow_review_activity_comment.md) | 8 | | BASE TABLE |
|
||||
| [public.workflow_review_request](public.workflow_review_request.md) | 12 | | BASE TABLE |
|
||||
| [public.workflow_review_request_authors](public.workflow_review_request_authors.md) | 2 | | BASE TABLE |
|
||||
| [public.workflow_review_request_reviewers](public.workflow_review_request_reviewers.md) | 2 | | BASE TABLE |
|
||||
| [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) | 4 | | BASE TABLE |
|
||||
| [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) | 5 | | BASE TABLE |
|
||||
| [public.workflow_statistics](public.workflow_statistics.md) | 7 | | BASE TABLE |
|
||||
| [public.workflow_statistics_delta](public.workflow_statistics_delta.md) | 6 | | BASE TABLE |
|
||||
| [public.workflows_tags](public.workflows_tags.md) | 2 | | BASE TABLE |
|
||||
|
|
@ -320,6 +322,11 @@ erDiagram
|
|||
"public.workflow_publish_history" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;versionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_published_version" |o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE RESTRICT"
|
||||
"public.workflow_published_version" }o--|| "public.workflow_history" : "FOREIGN KEY (#quot;publishedVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE RESTRICT"
|
||||
"public.workflow_review_activity" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity" }o--o| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_activity" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_activity_comment" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity_comment" }o--|| "public.workflow_review_activity" : "FOREIGN KEY (#quot;activityId#quot;) REFERENCES workflow_review_activity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;updatedById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;closedById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
|
|
@ -330,6 +337,7 @@ erDiagram
|
|||
"public.workflow_review_request_reviewers" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;workflowVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;baselineVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflows_tags" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflows_tags" }o--|| "public.tag_entity" : "FOREIGN KEY (#quot;tagId#quot;) REFERENCES tag_entity(id) ON DELETE CASCADE"
|
||||
|
|
@ -1506,6 +1514,26 @@ erDiagram
|
|||
timestamp_3__with_time_zone updatedAt
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_activity_comment" {
|
||||
integer activityId FK
|
||||
text body
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
timestamp_3__with_time_zone deletedAt
|
||||
json history
|
||||
integer id
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_review_request" {
|
||||
timestamp_3__with_time_zone approvedAt
|
||||
uuid closedById FK
|
||||
|
|
@ -1529,6 +1557,7 @@ erDiagram
|
|||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
| disabled | boolean | false | false | | | |
|
||||
| email | varchar(255) | | true | | | |
|
||||
| firstName | varchar(32) | | true | | | |
|
||||
| id | uuid | gen_random_uuid() | false | [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_eval_rating](public.agent_eval_rating.md) [public.agent_eval_run](public.agent_eval_run.md) [public.agent_history](public.agent_history.md) [public.auth_identity](public.auth_identity.md) [public.chat_hub_agents](public.chat_hub_agents.md) [public.chat_hub_sessions](public.chat_hub_sessions.md) [public.chat_hub_tools](public.chat_hub_tools.md) [public.dynamic_credential_user_entry](public.dynamic_credential_user_entry.md) [public.evaluation_collection](public.evaluation_collection.md) [public.instance_ai_mcp_registry_connections](public.instance_ai_mcp_registry_connections.md) [public.instance_ai_pending_confirmations](public.instance_ai_pending_confirmations.md) [public.instance_ai_thread_grants](public.instance_ai_thread_grants.md) [public.oauth_access_tokens](public.oauth_access_tokens.md) [public.oauth_authorization_codes](public.oauth_authorization_codes.md) [public.oauth_refresh_tokens](public.oauth_refresh_tokens.md) [public.oauth_user_consents](public.oauth_user_consents.md) [public.project](public.project.md) [public.project_relation](public.project_relation.md) [public.user_api_keys](public.user_api_keys.md) [public.user_favorites](public.user_favorites.md) [public.workflow_builder_session](public.workflow_builder_session.md) [public.workflow_publish_history](public.workflow_publish_history.md) [public.workflow_review_request](public.workflow_review_request.md) [public.workflow_review_request_authors](public.workflow_review_request_authors.md) [public.workflow_review_request_reviewers](public.workflow_review_request_reviewers.md) | | |
|
||||
| id | uuid | gen_random_uuid() | false | [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_eval_rating](public.agent_eval_rating.md) [public.agent_eval_run](public.agent_eval_run.md) [public.agent_history](public.agent_history.md) [public.auth_identity](public.auth_identity.md) [public.chat_hub_agents](public.chat_hub_agents.md) [public.chat_hub_sessions](public.chat_hub_sessions.md) [public.chat_hub_tools](public.chat_hub_tools.md) [public.dynamic_credential_user_entry](public.dynamic_credential_user_entry.md) [public.evaluation_collection](public.evaluation_collection.md) [public.instance_ai_mcp_registry_connections](public.instance_ai_mcp_registry_connections.md) [public.instance_ai_pending_confirmations](public.instance_ai_pending_confirmations.md) [public.instance_ai_thread_grants](public.instance_ai_thread_grants.md) [public.oauth_access_tokens](public.oauth_access_tokens.md) [public.oauth_authorization_codes](public.oauth_authorization_codes.md) [public.oauth_refresh_tokens](public.oauth_refresh_tokens.md) [public.oauth_user_consents](public.oauth_user_consents.md) [public.project](public.project.md) [public.project_relation](public.project_relation.md) [public.user_api_keys](public.user_api_keys.md) [public.user_favorites](public.user_favorites.md) [public.workflow_builder_session](public.workflow_builder_session.md) [public.workflow_publish_history](public.workflow_publish_history.md) [public.workflow_review_activity](public.workflow_review_activity.md) [public.workflow_review_activity_comment](public.workflow_review_activity_comment.md) [public.workflow_review_request](public.workflow_review_request.md) [public.workflow_review_request_authors](public.workflow_review_request_authors.md) [public.workflow_review_request_reviewers](public.workflow_review_request_reviewers.md) | | |
|
||||
| lastActiveAt | date | | true | | | |
|
||||
| lastName | varchar(32) | | true | | | |
|
||||
| mfaEnabled | boolean | false | false | | | |
|
||||
|
|
@ -70,6 +70,8 @@ erDiagram
|
|||
"public.user_favorites" }o--|| "public.user" : "FOREIGN KEY (#quot;userId#quot;) REFERENCES #quot;user#quot;(id) ON DELETE CASCADE"
|
||||
"public.workflow_builder_session" }o--|| "public.user" : "FOREIGN KEY (#quot;userId#quot;) REFERENCES #quot;user#quot;(id) ON DELETE CASCADE"
|
||||
"public.workflow_publish_history" }o--o| "public.user" : "FOREIGN KEY (#quot;userId#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity_comment" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;updatedById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;closedById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
|
|
@ -330,6 +332,26 @@ erDiagram
|
|||
varchar_36_ versionId FK
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_activity_comment" {
|
||||
integer activityId FK
|
||||
text body
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
timestamp_3__with_time_zone deletedAt
|
||||
json history
|
||||
integer id
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_review_request" {
|
||||
timestamp_3__with_time_zone approvedAt
|
||||
uuid closedById FK
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
| connections | json | | false | | | |
|
||||
| createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
|
||||
| description | text | | true | | | |
|
||||
| id | varchar(36) | | false | [public.ai_builder_temporary_workflow](public.ai_builder_temporary_workflow.md) [public.chat_hub_messages](public.chat_hub_messages.md) [public.chat_hub_sessions](public.chat_hub_sessions.md) [public.evaluation_collection](public.evaluation_collection.md) [public.evaluation_config](public.evaluation_config.md) [public.execution_entity](public.execution_entity.md) [public.insights_metadata](public.insights_metadata.md) [public.processed_data](public.processed_data.md) [public.shared_workflow](public.shared_workflow.md) [public.test_run](public.test_run.md) [public.webhook_entity](public.webhook_entity.md) [public.workflow_builder_session](public.workflow_builder_session.md) [public.workflow_dependency](public.workflow_dependency.md) [public.workflow_history](public.workflow_history.md) [public.workflow_publication_trigger_status](public.workflow_publication_trigger_status.md) [public.workflow_publish_history](public.workflow_publish_history.md) [public.workflow_published_version](public.workflow_published_version.md) [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) [public.workflows_tags](public.workflows_tags.md) | | |
|
||||
| id | varchar(36) | | false | [public.ai_builder_temporary_workflow](public.ai_builder_temporary_workflow.md) [public.chat_hub_messages](public.chat_hub_messages.md) [public.chat_hub_sessions](public.chat_hub_sessions.md) [public.evaluation_collection](public.evaluation_collection.md) [public.evaluation_config](public.evaluation_config.md) [public.execution_entity](public.execution_entity.md) [public.insights_metadata](public.insights_metadata.md) [public.processed_data](public.processed_data.md) [public.shared_workflow](public.shared_workflow.md) [public.test_run](public.test_run.md) [public.webhook_entity](public.webhook_entity.md) [public.workflow_builder_session](public.workflow_builder_session.md) [public.workflow_dependency](public.workflow_dependency.md) [public.workflow_history](public.workflow_history.md) [public.workflow_publication_trigger_status](public.workflow_publication_trigger_status.md) [public.workflow_publish_history](public.workflow_publish_history.md) [public.workflow_published_version](public.workflow_published_version.md) [public.workflow_review_activity](public.workflow_review_activity.md) [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) [public.workflows_tags](public.workflows_tags.md) | | |
|
||||
| isArchived | boolean | false | false | | | |
|
||||
| meta | json | | true | | | |
|
||||
| name | varchar(128) | | false | | | |
|
||||
|
|
@ -83,6 +83,7 @@ erDiagram
|
|||
"public.workflow_publication_trigger_status" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_publish_history" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_published_version" |o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE RESTRICT"
|
||||
"public.workflow_review_activity" }o--o| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflows_tags" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_entity" }o--o| "public.folder" : "FOREIGN KEY (#quot;parentFolderId#quot;) REFERENCES folder(id) ON DELETE CASCADE"
|
||||
|
|
@ -301,7 +302,18 @@ erDiagram
|
|||
timestamp_3__with_time_zone updatedAt
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ erDiagram
|
|||
"public.workflow_publish_history" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;versionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_published_version" }o--|| "public.workflow_history" : "FOREIGN KEY (#quot;publishedVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE RESTRICT"
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;workflowVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;baselineVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_history" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
|
||||
"public.workflow_history" {
|
||||
|
|
@ -111,6 +112,7 @@ erDiagram
|
|||
varchar_36_ workflowId FK
|
||||
}
|
||||
"public.workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
# public.workflow_review_activity
|
||||
|
||||
## Columns
|
||||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
|
||||
| createdById | uuid | | true | | [public.user](public.user.md) | |
|
||||
| data | json | | true | | | Detail per activity type |
|
||||
| id | integer | | false | [public.workflow_review_activity_comment](public.workflow_review_activity_comment.md) | | |
|
||||
| type | varchar(64) | | false | | | Feed entry kind; see WorkflowReviewActivityType in @n8n/api-types |
|
||||
| typeVersion | integer | 1 | false | | | Schema version of the `data` payload for this `type` |
|
||||
| workflowId | varchar(36) | | true | | [public.workflow_entity](public.workflow_entity.md) | Scopes the entry to one workflow; NULL for review-level entries like comments |
|
||||
| workflowReviewRequestId | varchar(36) | | false | | [public.workflow_review_request](public.workflow_review_request.md) | |
|
||||
|
||||
## Constraints
|
||||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| CHK_workflow_review_activity_type | CHECK | CHECK (((type)::text = ANY ((ARRAY['review.opened'::character varying, 'comment.created'::character varying, 'review.changes_requested'::character varying, 'review.version_updated'::character varying, 'review.approved'::character varying, 'workflow.published'::character varying, 'review.closed'::character varying])::text[]))) |
|
||||
| FK_61048bf6220dd354c955a9d9379 | FOREIGN KEY | FOREIGN KEY ("workflowReviewRequestId") REFERENCES workflow_review_request(id) ON DELETE CASCADE |
|
||||
| FK_d0162c063ffb699b0f1d1172508 | FOREIGN KEY | FOREIGN KEY ("workflowId") REFERENCES workflow_entity(id) ON DELETE CASCADE |
|
||||
| FK_fcf78b037a72fc7aa01ab237e08 | FOREIGN KEY | FOREIGN KEY ("createdById") REFERENCES "user"(id) ON DELETE SET NULL |
|
||||
| PK_f1e66ba0b645dec566057ad21e6 | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
| workflow_review_activity_createdAt_not_null | n | NOT NULL "createdAt" |
|
||||
| workflow_review_activity_id_not_null | n | NOT NULL id |
|
||||
| workflow_review_activity_typeVersion_not_null | n | NOT NULL "typeVersion" |
|
||||
| workflow_review_activity_type_not_null | n | NOT NULL type |
|
||||
| workflow_review_activity_workflowReviewRequestId_not_null | n | NOT NULL "workflowReviewRequestId" |
|
||||
|
||||
## Indexes
|
||||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_activity_request | CREATE INDEX "IDX_workflow_review_activity_request" ON public.workflow_review_activity USING btree ("workflowReviewRequestId", id) |
|
||||
| PK_f1e66ba0b645dec566057ad21e6 | CREATE UNIQUE INDEX "PK_f1e66ba0b645dec566057ad21e6" ON public.workflow_review_activity USING btree (id) |
|
||||
|
||||
## Relations
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"public.workflow_review_activity" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity_comment" }o--|| "public.workflow_review_activity" : "FOREIGN KEY (#quot;activityId#quot;) REFERENCES workflow_review_activity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_activity" }o--o| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_activity" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.user" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
boolean disabled
|
||||
varchar_255_ email
|
||||
varchar_32_ firstName
|
||||
uuid id
|
||||
date lastActiveAt
|
||||
varchar_32_ lastName
|
||||
boolean mfaEnabled
|
||||
text mfaRecoveryCodes
|
||||
text mfaSecret
|
||||
varchar_255_ password
|
||||
json personalizationAnswers
|
||||
varchar_128_ roleSlug FK
|
||||
json settings
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_review_activity_comment" {
|
||||
integer activityId FK
|
||||
text body
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
timestamp_3__with_time_zone deletedAt
|
||||
json history
|
||||
integer id
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_entity" {
|
||||
boolean active
|
||||
varchar_36_ activeVersionId FK
|
||||
json connections
|
||||
timestamp_3__with_time_zone createdAt
|
||||
text description
|
||||
varchar_36_ id
|
||||
boolean isArchived
|
||||
json meta
|
||||
varchar_128_ name
|
||||
json nodeGroups
|
||||
json nodes
|
||||
varchar_36_ parentFolderId FK
|
||||
json pinData
|
||||
json settings
|
||||
varchar sourceWorkflowId
|
||||
json staticData
|
||||
integer triggerCount
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
integer versionCounter
|
||||
character_36_ versionId
|
||||
}
|
||||
"public.workflow_review_request" {
|
||||
timestamp_3__with_time_zone approvedAt
|
||||
uuid closedById FK
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
varchar_50_ decision
|
||||
text description
|
||||
varchar_36_ id
|
||||
varchar_36_ projectId FK
|
||||
varchar_16_ state
|
||||
varchar_255_ title
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
uuid updatedById FK
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> Generated by [tbls](https://github.com/k1LoW/tbls)
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
# public.workflow_review_activity_comment
|
||||
|
||||
## Columns
|
||||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| activityId | integer | | false | | [public.workflow_review_activity](public.workflow_review_activity.md) | Thread this message belongs to; the activity row is its header |
|
||||
| body | text | | true | | | Only user-editable text in the feed; nulled on delete |
|
||||
| createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
|
||||
| createdById | uuid | | true | | [public.user](public.user.md) | |
|
||||
| deletedAt | timestamp(3) with time zone | | true | | | Set when the comment is deleted |
|
||||
| history | json | | true | | | Comment revision history. |
|
||||
| id | integer | | false | | | |
|
||||
| updatedAt | timestamp(3) with time zone | | true | | | Set when the body is edited |
|
||||
|
||||
## Constraints
|
||||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| FK_3cd755e8ce44ee49bdf7e9222ec | FOREIGN KEY | FOREIGN KEY ("activityId") REFERENCES workflow_review_activity(id) ON DELETE CASCADE |
|
||||
| FK_9cfac4b106553d0930a1962bea0 | FOREIGN KEY | FOREIGN KEY ("createdById") REFERENCES "user"(id) ON DELETE SET NULL |
|
||||
| PK_5a2a7dea8b7e6afa1001129da27 | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
| workflow_review_activity_comment_activityId_not_null | n | NOT NULL "activityId" |
|
||||
| workflow_review_activity_comment_createdAt_not_null | n | NOT NULL "createdAt" |
|
||||
| workflow_review_activity_comment_id_not_null | n | NOT NULL id |
|
||||
|
||||
## Indexes
|
||||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_activity_comment_activity | CREATE INDEX "IDX_workflow_review_activity_comment_activity" ON public.workflow_review_activity_comment USING btree ("activityId", id) |
|
||||
| PK_5a2a7dea8b7e6afa1001129da27 | CREATE UNIQUE INDEX "PK_5a2a7dea8b7e6afa1001129da27" ON public.workflow_review_activity_comment USING btree (id) |
|
||||
|
||||
## Relations
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"public.workflow_review_activity_comment" }o--|| "public.workflow_review_activity" : "FOREIGN KEY (#quot;activityId#quot;) REFERENCES workflow_review_activity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_activity_comment" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
|
||||
"public.workflow_review_activity_comment" {
|
||||
integer activityId FK
|
||||
text body
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
timestamp_3__with_time_zone deletedAt
|
||||
json history
|
||||
integer id
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.user" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
boolean disabled
|
||||
varchar_255_ email
|
||||
varchar_32_ firstName
|
||||
uuid id
|
||||
date lastActiveAt
|
||||
varchar_32_ lastName
|
||||
boolean mfaEnabled
|
||||
text mfaRecoveryCodes
|
||||
text mfaSecret
|
||||
varchar_255_ password
|
||||
json personalizationAnswers
|
||||
varchar_128_ roleSlug FK
|
||||
json settings
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> Generated by [tbls](https://github.com/k1LoW/tbls)
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
| createdById | uuid | | true | | [public.user](public.user.md) | |
|
||||
| decision | varchar(50) | 'pending'::character varying | false | | | Latest review outcome while the request is open |
|
||||
| description | text | | true | | | |
|
||||
| id | varchar(36) | | false | [public.workflow_review_request_authors](public.workflow_review_request_authors.md) [public.workflow_review_request_reviewers](public.workflow_review_request_reviewers.md) [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) | | |
|
||||
| id | varchar(36) | | false | [public.workflow_review_activity](public.workflow_review_activity.md) [public.workflow_review_request_authors](public.workflow_review_request_authors.md) [public.workflow_review_request_reviewers](public.workflow_review_request_reviewers.md) [public.workflow_review_request_workflow](public.workflow_review_request_workflow.md) | | |
|
||||
| projectId | varchar(36) | | false | | [public.project](public.project.md) | |
|
||||
| state | varchar(16) | 'open'::character varying | false | | | Review lifecycle: open reviews accept actions; closed reviews are done |
|
||||
| title | varchar(255) | | false | | | |
|
||||
|
|
@ -50,6 +50,7 @@ erDiagram
|
|||
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;closedById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_request" }o--o| "public.user" : "FOREIGN KEY (#quot;createdById#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
|
||||
"public.workflow_review_activity" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_authors" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_reviewers" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
|
|
@ -87,6 +88,16 @@ erDiagram
|
|||
json settings
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
}
|
||||
"public.workflow_review_activity" {
|
||||
timestamp_3__with_time_zone createdAt
|
||||
uuid createdById FK
|
||||
json data
|
||||
integer id
|
||||
varchar_64_ type
|
||||
integer typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_request_authors" {
|
||||
uuid userId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
@ -96,6 +107,7 @@ erDiagram
|
|||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"public.workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_request_authors_user | CREATE INDEX "IDX_workflow_review_request_authors_user" ON public.workflow_review_request_authors USING btree ("userId", "workflowReviewRequestId") |
|
||||
| PK_ae197bb5135d422f6392a879e0c | CREATE UNIQUE INDEX "PK_ae197bb5135d422f6392a879e0c" ON public.workflow_review_request_authors USING btree ("workflowReviewRequestId", "userId") |
|
||||
|
||||
## Relations
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| baselineVersionId | varchar(36) | | true | | [public.workflow_history](public.workflow_history.md) | Published workflow_history version captured when the review was approved |
|
||||
| id | varchar(36) | | false | | | |
|
||||
| workflowId | varchar(36) | | false | | [public.workflow_entity](public.workflow_entity.md) | |
|
||||
| workflowReviewRequestId | varchar(36) | | false | | [public.workflow_review_request](public.workflow_review_request.md) | |
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
| FK_0f6f0f2c6d46b806fee02962ac2 | FOREIGN KEY | FOREIGN KEY ("workflowVersionId") REFERENCES workflow_history("versionId") ON DELETE SET NULL |
|
||||
| FK_619f5b0544bcec60c3387e82f2f | FOREIGN KEY | FOREIGN KEY ("workflowId") REFERENCES workflow_entity(id) ON DELETE CASCADE |
|
||||
| FK_e44b652e6dc99ef1364a2d85504 | FOREIGN KEY | FOREIGN KEY ("workflowReviewRequestId") REFERENCES workflow_review_request(id) ON DELETE CASCADE |
|
||||
| FK_workflow_review_request_workflow_baselineVersionId | FOREIGN KEY | FOREIGN KEY ("baselineVersionId") REFERENCES workflow_history("versionId") ON DELETE SET NULL |
|
||||
| PK_be3bf4facb054cf2b2b116b3b9c | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
| workflow_review_request_workfl_workflowReviewRequestId_not_null | n | NOT NULL "workflowReviewRequestId" |
|
||||
| workflow_review_request_workflow_id_not_null | n | NOT NULL id |
|
||||
|
|
@ -34,16 +36,31 @@
|
|||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;baselineVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--|| "public.workflow_review_request" : "FOREIGN KEY (#quot;workflowReviewRequestId#quot;) REFERENCES workflow_review_request(id) ON DELETE CASCADE"
|
||||
"public.workflow_review_request_workflow" }o--o| "public.workflow_history" : "FOREIGN KEY (#quot;workflowVersionId#quot;) REFERENCES workflow_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
|
||||
"public.workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
varchar_36_ workflowVersionId FK
|
||||
}
|
||||
"public.workflow_history" {
|
||||
varchar_255_ authors
|
||||
boolean autosaved
|
||||
json connections
|
||||
timestamp_3__with_time_zone createdAt
|
||||
text description
|
||||
varchar_128_ name
|
||||
json nodeGroups
|
||||
json nodes
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
varchar_36_ versionId
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"public.workflow_entity" {
|
||||
boolean active
|
||||
varchar_36_ activeVersionId FK
|
||||
|
|
@ -80,19 +97,6 @@ erDiagram
|
|||
timestamp_3__with_time_zone updatedAt
|
||||
uuid updatedById FK
|
||||
}
|
||||
"public.workflow_history" {
|
||||
varchar_255_ authors
|
||||
boolean autosaved
|
||||
json connections
|
||||
timestamp_3__with_time_zone createdAt
|
||||
text description
|
||||
varchar_128_ name
|
||||
json nodeGroups
|
||||
json nodes
|
||||
timestamp_3__with_time_zone updatedAt
|
||||
varchar_36_ versionId
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -126,10 +126,12 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
|
|||
| [workflow_publication_trigger_status](workflow_publication_trigger_status.md) | 8 | | table |
|
||||
| [workflow_publish_history](workflow_publish_history.md) | 6 | | table |
|
||||
| [workflow_published_version](workflow_published_version.md) | 4 | | table |
|
||||
| [workflow_review_activity](workflow_review_activity.md) | 8 | | table |
|
||||
| [workflow_review_activity_comment](workflow_review_activity_comment.md) | 8 | | table |
|
||||
| [workflow_review_request](workflow_review_request.md) | 12 | | table |
|
||||
| [workflow_review_request_authors](workflow_review_request_authors.md) | 2 | | table |
|
||||
| [workflow_review_request_reviewers](workflow_review_request_reviewers.md) | 2 | | table |
|
||||
| [workflow_review_request_workflow](workflow_review_request_workflow.md) | 4 | | table |
|
||||
| [workflow_review_request_workflow](workflow_review_request_workflow.md) | 5 | | table |
|
||||
| [workflow_statistics](workflow_statistics.md) | 7 | | table |
|
||||
| [workflows_tags](workflows_tags.md) | 2 | | table |
|
||||
|
||||
|
|
@ -307,6 +309,11 @@ erDiagram
|
|||
"workflow_published_version" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE RESTRICT MATCH NONE"
|
||||
"workflow_published_version" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_published_version" }o--|| "workflow_history" : "FOREIGN KEY (publishedVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity" }o--o| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity_comment" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity_comment" }o--|| "workflow_review_activity" : "FOREIGN KEY (activityId) REFERENCES workflow_review_activity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (closedById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (updatedById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
|
|
@ -315,9 +322,10 @@ erDiagram
|
|||
"workflow_review_request_authors" |o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_reviewers" |o--|| "user" : "FOREIGN KEY (userId) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_reviewers" |o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (baselineVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflows_tags" |o--|| "tag_entity" : "FOREIGN KEY (tagId) REFERENCES tag_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflows_tags" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
||||
|
|
@ -1495,6 +1503,26 @@ erDiagram
|
|||
datetime_3_ updatedAt
|
||||
varchar_36_ workflowId PK
|
||||
}
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"workflow_review_activity_comment" {
|
||||
INTEGER activityId FK
|
||||
TEXT body
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
datetime_3_ deletedAt
|
||||
TEXT history
|
||||
INTEGER id
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_review_request" {
|
||||
datetime_3_ approvedAt
|
||||
varchar closedById FK
|
||||
|
|
@ -1518,6 +1546,7 @@ erDiagram
|
|||
varchar_36_ workflowReviewRequestId PK
|
||||
}
|
||||
"workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id PK
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ CREATE TABLE "user" ("id" varchar PRIMARY KEY, "email" varchar(255), "firstName"
|
|||
| disabled | boolean | FALSE | false | | | |
|
||||
| email | varchar(255) | | true | | | |
|
||||
| firstName | varchar(32) | | true | | | |
|
||||
| id | varchar | | true | [agent_eval_dataset](agent_eval_dataset.md) [agent_eval_rating](agent_eval_rating.md) [agent_eval_run](agent_eval_run.md) [agent_history](agent_history.md) [auth_identity](auth_identity.md) [chat_hub_agents](chat_hub_agents.md) [chat_hub_sessions](chat_hub_sessions.md) [chat_hub_tools](chat_hub_tools.md) [dynamic_credential_user_entry](dynamic_credential_user_entry.md) [evaluation_collection](evaluation_collection.md) [instance_ai_mcp_registry_connections](instance_ai_mcp_registry_connections.md) [instance_ai_pending_confirmations](instance_ai_pending_confirmations.md) [instance_ai_thread_grants](instance_ai_thread_grants.md) [oauth_access_tokens](oauth_access_tokens.md) [oauth_authorization_codes](oauth_authorization_codes.md) [oauth_refresh_tokens](oauth_refresh_tokens.md) [oauth_user_consents](oauth_user_consents.md) [project](project.md) [project_relation](project_relation.md) [user_api_keys](user_api_keys.md) [user_favorites](user_favorites.md) [workflow_builder_session](workflow_builder_session.md) [workflow_publish_history](workflow_publish_history.md) [workflow_review_request](workflow_review_request.md) [workflow_review_request_authors](workflow_review_request_authors.md) [workflow_review_request_reviewers](workflow_review_request_reviewers.md) | | |
|
||||
| id | varchar | | true | [agent_eval_dataset](agent_eval_dataset.md) [agent_eval_rating](agent_eval_rating.md) [agent_eval_run](agent_eval_run.md) [agent_history](agent_history.md) [auth_identity](auth_identity.md) [chat_hub_agents](chat_hub_agents.md) [chat_hub_sessions](chat_hub_sessions.md) [chat_hub_tools](chat_hub_tools.md) [dynamic_credential_user_entry](dynamic_credential_user_entry.md) [evaluation_collection](evaluation_collection.md) [instance_ai_mcp_registry_connections](instance_ai_mcp_registry_connections.md) [instance_ai_pending_confirmations](instance_ai_pending_confirmations.md) [instance_ai_thread_grants](instance_ai_thread_grants.md) [oauth_access_tokens](oauth_access_tokens.md) [oauth_authorization_codes](oauth_authorization_codes.md) [oauth_refresh_tokens](oauth_refresh_tokens.md) [oauth_user_consents](oauth_user_consents.md) [project](project.md) [project_relation](project_relation.md) [user_api_keys](user_api_keys.md) [user_favorites](user_favorites.md) [workflow_builder_session](workflow_builder_session.md) [workflow_publish_history](workflow_publish_history.md) [workflow_review_activity](workflow_review_activity.md) [workflow_review_activity_comment](workflow_review_activity_comment.md) [workflow_review_request](workflow_review_request.md) [workflow_review_request_authors](workflow_review_request_authors.md) [workflow_review_request_reviewers](workflow_review_request_reviewers.md) | | |
|
||||
| lastActiveAt | date | | true | | | |
|
||||
| lastName | varchar(32) | | true | | | |
|
||||
| mfaEnabled | boolean | FALSE | false | | | |
|
||||
|
|
@ -76,6 +76,8 @@ erDiagram
|
|||
"user_favorites" }o--|| "user" : "FOREIGN KEY (userId) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_builder_session" }o--|| "user" : "FOREIGN KEY (userId) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_publish_history" }o--o| "user" : "FOREIGN KEY (userId) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity_comment" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (closedById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (updatedById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
|
|
@ -336,6 +338,26 @@ erDiagram
|
|||
varchar_36_ versionId FK
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"workflow_review_activity_comment" {
|
||||
INTEGER activityId FK
|
||||
TEXT body
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
datetime_3_ deletedAt
|
||||
TEXT history
|
||||
INTEGER id
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_review_request" {
|
||||
datetime_3_ approvedAt
|
||||
varchar closedById FK
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ CREATE TABLE "workflow_entity" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" va
|
|||
| connections | TEXT | | true | | | |
|
||||
| createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
|
||||
| description | TEXT | | true | | | |
|
||||
| id | varchar(36) | | false | [ai_builder_temporary_workflow](ai_builder_temporary_workflow.md) [chat_hub_messages](chat_hub_messages.md) [chat_hub_sessions](chat_hub_sessions.md) [evaluation_collection](evaluation_collection.md) [evaluation_config](evaluation_config.md) [execution_entity](execution_entity.md) [insights_metadata](insights_metadata.md) [processed_data](processed_data.md) [shared_workflow](shared_workflow.md) [test_run](test_run.md) [workflow_builder_session](workflow_builder_session.md) [workflow_dependency](workflow_dependency.md) [workflow_history](workflow_history.md) [workflow_publication_trigger_status](workflow_publication_trigger_status.md) [workflow_publish_history](workflow_publish_history.md) [workflow_published_version](workflow_published_version.md) [workflow_review_request_workflow](workflow_review_request_workflow.md) [workflows_tags](workflows_tags.md) | | |
|
||||
| id | varchar(36) | | false | [ai_builder_temporary_workflow](ai_builder_temporary_workflow.md) [chat_hub_messages](chat_hub_messages.md) [chat_hub_sessions](chat_hub_sessions.md) [evaluation_collection](evaluation_collection.md) [evaluation_config](evaluation_config.md) [execution_entity](execution_entity.md) [insights_metadata](insights_metadata.md) [processed_data](processed_data.md) [shared_workflow](shared_workflow.md) [test_run](test_run.md) [workflow_builder_session](workflow_builder_session.md) [workflow_dependency](workflow_dependency.md) [workflow_history](workflow_history.md) [workflow_publication_trigger_status](workflow_publication_trigger_status.md) [workflow_publish_history](workflow_publish_history.md) [workflow_published_version](workflow_published_version.md) [workflow_review_activity](workflow_review_activity.md) [workflow_review_request_workflow](workflow_review_request_workflow.md) [workflows_tags](workflows_tags.md) | | |
|
||||
| isArchived | boolean | FALSE | false | | | |
|
||||
| meta | TEXT | | true | | | |
|
||||
| name | varchar(128) | | false | | | |
|
||||
|
|
@ -82,6 +82,7 @@ erDiagram
|
|||
"workflow_publish_history" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_published_version" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE RESTRICT MATCH NONE"
|
||||
"workflow_published_version" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity" }o--o| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflows_tags" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_entity" }o--o| "folder" : "FOREIGN KEY (parentFolderId) REFERENCES folder (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
|
@ -292,7 +293,18 @@ erDiagram
|
|||
datetime_3_ updatedAt
|
||||
varchar_36_ workflowId PK
|
||||
}
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id PK
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ erDiagram
|
|||
"workflow_publish_history" }o--o| "workflow_history" : "FOREIGN KEY (versionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_published_version" }o--|| "workflow_history" : "FOREIGN KEY (publishedVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE RESTRICT MATCH NONE"
|
||||
"workflow_published_version" }o--|| "workflow_history" : "FOREIGN KEY (publishedVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (baselineVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_history" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ erDiagram
|
|||
varchar_36_ workflowId PK
|
||||
}
|
||||
"workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id PK
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
130
docs/generated/sqlite-schema/workflow_review_activity.md
Normal file
130
docs/generated/sqlite-schema/workflow_review_activity.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# workflow_review_activity
|
||||
|
||||
## Description
|
||||
|
||||
<details>
|
||||
<summary><strong>Table Definition</strong></summary>
|
||||
|
||||
```sql
|
||||
CREATE TABLE "workflow_review_activity" ("id" integer PRIMARY KEY NOT NULL, "workflowReviewRequestId" varchar(36) NOT NULL, "type" varchar(64) NOT NULL, "typeVersion" integer NOT NULL DEFAULT (1), "data" text, "createdById" varchar, "workflowId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), CONSTRAINT "CHK_workflow_review_activity_type" CHECK ("type" IN ('review.opened', 'comment.created', 'review.changes_requested', 'review.version_updated', 'review.approved', 'workflow.published', 'review.closed')), CONSTRAINT "FK_61048bf6220dd354c955a9d9379" FOREIGN KEY ("workflowReviewRequestId") REFERENCES "workflow_review_request" ("id") ON DELETE CASCADE, CONSTRAINT "FK_fcf78b037a72fc7aa01ab237e08" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE SET NULL, CONSTRAINT "FK_d0162c063ffb699b0f1d1172508" FOREIGN KEY ("workflowId") REFERENCES "workflow_entity" ("id") ON DELETE CASCADE)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Columns
|
||||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
|
||||
| createdById | varchar | | true | | [user](user.md) | |
|
||||
| data | TEXT | | true | | | |
|
||||
| id | INTEGER | | false | [workflow_review_activity_comment](workflow_review_activity_comment.md) | | |
|
||||
| type | varchar(64) | | false | | | |
|
||||
| typeVersion | INTEGER | 1 | false | | | |
|
||||
| workflowId | varchar(36) | | true | | [workflow_entity](workflow_entity.md) | |
|
||||
| workflowReviewRequestId | varchar(36) | | false | | [workflow_review_request](workflow_review_request.md) | |
|
||||
|
||||
## Constraints
|
||||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| - | CHECK | CHECK ("type" IN ('review.opened', 'comment.created', 'review.changes_requested', 'review.version_updated', 'review.approved', 'workflow.published', 'review.closed')) |
|
||||
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| - (Foreign key ID: 2) | FOREIGN KEY | FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| id | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
|
||||
## Indexes
|
||||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_activity_request | CREATE INDEX "IDX_workflow_review_activity_request" ON "workflow_review_activity" ("workflowReviewRequestId", "id") |
|
||||
|
||||
## Relations
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"workflow_review_activity" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity_comment" }o--|| "workflow_review_activity" : "FOREIGN KEY (activityId) REFERENCES workflow_review_activity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity" }o--o| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"user" {
|
||||
datetime_3_ createdAt
|
||||
boolean disabled
|
||||
varchar_255_ email
|
||||
varchar_32_ firstName
|
||||
varchar id PK
|
||||
date lastActiveAt
|
||||
varchar_32_ lastName
|
||||
boolean mfaEnabled
|
||||
TEXT mfaRecoveryCodes
|
||||
TEXT mfaSecret
|
||||
varchar password
|
||||
TEXT personalizationAnswers
|
||||
varchar_128_ roleSlug FK
|
||||
TEXT settings
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_review_activity_comment" {
|
||||
INTEGER activityId FK
|
||||
TEXT body
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
datetime_3_ deletedAt
|
||||
TEXT history
|
||||
INTEGER id
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_entity" {
|
||||
boolean active
|
||||
varchar_36_ activeVersionId FK
|
||||
TEXT connections
|
||||
datetime_3_ createdAt
|
||||
TEXT description
|
||||
varchar_36_ id PK
|
||||
boolean isArchived
|
||||
TEXT meta
|
||||
varchar_128_ name
|
||||
TEXT nodeGroups
|
||||
TEXT nodes
|
||||
varchar_36_ parentFolderId FK
|
||||
TEXT pinData
|
||||
TEXT settings
|
||||
varchar sourceWorkflowId
|
||||
TEXT staticData
|
||||
INTEGER triggerCount
|
||||
datetime_3_ updatedAt
|
||||
INTEGER versionCounter
|
||||
varchar_36_ versionId
|
||||
}
|
||||
"workflow_review_request" {
|
||||
datetime_3_ approvedAt
|
||||
varchar closedById FK
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
varchar_50_ decision
|
||||
TEXT description
|
||||
varchar_36_ id PK
|
||||
varchar_36_ projectId FK
|
||||
varchar_16_ state
|
||||
varchar_255_ title
|
||||
datetime_3_ updatedAt
|
||||
varchar updatedById FK
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> Generated by [tbls](https://github.com/k1LoW/tbls)
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# workflow_review_activity_comment
|
||||
|
||||
## Description
|
||||
|
||||
<details>
|
||||
<summary><strong>Table Definition</strong></summary>
|
||||
|
||||
```sql
|
||||
CREATE TABLE "workflow_review_activity_comment" ("id" integer PRIMARY KEY NOT NULL, "activityId" integer NOT NULL, "createdById" varchar, "body" text, "history" text, "updatedAt" datetime(3), "deletedAt" datetime(3), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), CONSTRAINT "FK_3cd755e8ce44ee49bdf7e9222ec" FOREIGN KEY ("activityId") REFERENCES "workflow_review_activity" ("id") ON DELETE CASCADE, CONSTRAINT "FK_9cfac4b106553d0930a1962bea0" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE SET NULL)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Columns
|
||||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| activityId | INTEGER | | false | | [workflow_review_activity](workflow_review_activity.md) | |
|
||||
| body | TEXT | | true | | | |
|
||||
| createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
|
||||
| createdById | varchar | | true | | [user](user.md) | |
|
||||
| deletedAt | datetime(3) | | true | | | |
|
||||
| history | TEXT | | true | | | |
|
||||
| id | INTEGER | | false | | | |
|
||||
| updatedAt | datetime(3) | | true | | | |
|
||||
|
||||
## Constraints
|
||||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (activityId) REFERENCES workflow_review_activity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| id | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
|
||||
## Indexes
|
||||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_activity_comment_activity | CREATE INDEX "IDX_workflow_review_activity_comment_activity" ON "workflow_review_activity_comment" ("activityId", "id") |
|
||||
|
||||
## Relations
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"workflow_review_activity_comment" }o--|| "workflow_review_activity" : "FOREIGN KEY (activityId) REFERENCES workflow_review_activity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_activity_comment" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
|
||||
"workflow_review_activity_comment" {
|
||||
INTEGER activityId FK
|
||||
TEXT body
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
datetime_3_ deletedAt
|
||||
TEXT history
|
||||
INTEGER id
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"user" {
|
||||
datetime_3_ createdAt
|
||||
boolean disabled
|
||||
varchar_255_ email
|
||||
varchar_32_ firstName
|
||||
varchar id PK
|
||||
date lastActiveAt
|
||||
varchar_32_ lastName
|
||||
boolean mfaEnabled
|
||||
TEXT mfaRecoveryCodes
|
||||
TEXT mfaSecret
|
||||
varchar password
|
||||
TEXT personalizationAnswers
|
||||
varchar_128_ roleSlug FK
|
||||
TEXT settings
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> Generated by [tbls](https://github.com/k1LoW/tbls)
|
||||
|
|
@ -21,7 +21,7 @@ CREATE TABLE "workflow_review_request" ("id" varchar(36) PRIMARY KEY NOT NULL, "
|
|||
| createdById | varchar | | true | | [user](user.md) | |
|
||||
| decision | varchar(50) | 'pending' | false | | | |
|
||||
| description | TEXT | | true | | | |
|
||||
| id | varchar(36) | | false | [workflow_review_request_authors](workflow_review_request_authors.md) [workflow_review_request_reviewers](workflow_review_request_reviewers.md) [workflow_review_request_workflow](workflow_review_request_workflow.md) | | |
|
||||
| id | varchar(36) | | false | [workflow_review_activity](workflow_review_activity.md) [workflow_review_request_authors](workflow_review_request_authors.md) [workflow_review_request_reviewers](workflow_review_request_reviewers.md) [workflow_review_request_workflow](workflow_review_request_workflow.md) | | |
|
||||
| projectId | varchar(36) | | false | | [project](project.md) | |
|
||||
| state | varchar(16) | 'open' | false | | | |
|
||||
| title | varchar(255) | | false | | | |
|
||||
|
|
@ -55,6 +55,7 @@ erDiagram
|
|||
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (closedById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request" }o--o| "user" : "FOREIGN KEY (createdById) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_activity" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_authors" |o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_reviewers" |o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
|
@ -92,6 +93,16 @@ erDiagram
|
|||
TEXT settings
|
||||
datetime_3_ updatedAt
|
||||
}
|
||||
"workflow_review_activity" {
|
||||
datetime_3_ createdAt
|
||||
varchar createdById FK
|
||||
TEXT data
|
||||
INTEGER id
|
||||
varchar_64_ type
|
||||
INTEGER typeVersion
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
}
|
||||
"workflow_review_request_authors" {
|
||||
varchar userId PK
|
||||
varchar_36_ workflowReviewRequestId PK
|
||||
|
|
@ -101,6 +112,7 @@ erDiagram
|
|||
varchar_36_ workflowReviewRequestId PK
|
||||
}
|
||||
"workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id PK
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ CREATE TABLE "workflow_review_request_authors" ("workflowReviewRequestId" varcha
|
|||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_request_authors_user | CREATE INDEX "IDX_workflow_review_request_authors_user" ON "workflow_review_request_authors" ("userId", "workflowReviewRequestId") |
|
||||
| sqlite_autoindex_workflow_review_request_authors_1 | PRIMARY KEY (workflowReviewRequestId, userId) |
|
||||
|
||||
## Relations
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<summary><strong>Table Definition</strong></summary>
|
||||
|
||||
```sql
|
||||
CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NOT NULL, "workflowReviewRequestId" varchar(36) NOT NULL, "workflowId" varchar(36) NOT NULL, "workflowVersionId" varchar(36), CONSTRAINT "FK_e44b652e6dc99ef1364a2d85504" FOREIGN KEY ("workflowReviewRequestId") REFERENCES "workflow_review_request" ("id") ON DELETE CASCADE, CONSTRAINT "FK_619f5b0544bcec60c3387e82f2f" FOREIGN KEY ("workflowId") REFERENCES "workflow_entity" ("id") ON DELETE CASCADE, CONSTRAINT "FK_0f6f0f2c6d46b806fee02962ac2" FOREIGN KEY ("workflowVersionId") REFERENCES "workflow_history" ("versionId") ON DELETE SET NULL)
|
||||
CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NOT NULL, "workflowReviewRequestId" varchar(36) NOT NULL, "workflowId" varchar(36) NOT NULL, "workflowVersionId" varchar(36), "baselineVersionId" varchar(36), CONSTRAINT "FK_0f6f0f2c6d46b806fee02962ac2" FOREIGN KEY ("workflowVersionId") REFERENCES "workflow_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_619f5b0544bcec60c3387e82f2f" FOREIGN KEY ("workflowId") REFERENCES "workflow_entity" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_e44b652e6dc99ef1364a2d85504" FOREIGN KEY ("workflowReviewRequestId") REFERENCES "workflow_review_request" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_workflow_review_request_workflow_baselineVersionId" FOREIGN KEY ("baselineVersionId") REFERENCES "workflow_history" ("versionId") ON DELETE SET NULL)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
@ -15,6 +15,7 @@ CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NO
|
|||
|
||||
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
||||
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
||||
| baselineVersionId | varchar(36) | | true | | [workflow_history](workflow_history.md) | |
|
||||
| id | varchar(36) | | false | | | |
|
||||
| workflowId | varchar(36) | | false | | [workflow_entity](workflow_entity.md) | |
|
||||
| workflowReviewRequestId | varchar(36) | | false | | [workflow_review_request](workflow_review_request.md) | |
|
||||
|
|
@ -24,9 +25,10 @@ CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NO
|
|||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 2) | FOREIGN KEY | FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (baselineVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 2) | FOREIGN KEY | FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 3) | FOREIGN KEY | FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| id | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
| sqlite_autoindex_workflow_review_request_workflow_1 | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
|
||||
|
|
@ -34,8 +36,8 @@ CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NO
|
|||
|
||||
| Name | Definition |
|
||||
| ---- | ---------- |
|
||||
| IDX_workflow_review_request_workflow_workflow_request | CREATE INDEX "IDX_workflow_review_request_workflow_workflow_request"<br /> ON "workflow_review_request_workflow"("workflowId", "workflowReviewRequestId") |
|
||||
| UQ_workflow_review_request_workflow_request_workflow | CREATE UNIQUE INDEX "UQ_workflow_review_request_workflow_request_workflow"<br /> ON "workflow_review_request_workflow"("workflowReviewRequestId", "workflowId") |
|
||||
| IDX_workflow_review_request_workflow_workflow_request | CREATE INDEX "IDX_workflow_review_request_workflow_workflow_request" ON "workflow_review_request_workflow" ("workflowId", "workflowReviewRequestId") |
|
||||
| UQ_workflow_review_request_workflow_request_workflow | CREATE UNIQUE INDEX "UQ_workflow_review_request_workflow_request_workflow" ON "workflow_review_request_workflow" ("workflowReviewRequestId", "workflowId") |
|
||||
| sqlite_autoindex_workflow_review_request_workflow_1 | PRIMARY KEY (id) |
|
||||
|
||||
## Relations
|
||||
|
|
@ -43,16 +45,31 @@ CREATE TABLE "workflow_review_request_workflow" ("id" varchar(36) PRIMARY KEY NO
|
|||
```mermaid
|
||||
erDiagram
|
||||
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (baselineVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--|| "workflow_review_request" : "FOREIGN KEY (workflowReviewRequestId) REFERENCES workflow_review_request (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"workflow_review_request_workflow" }o--o| "workflow_history" : "FOREIGN KEY (workflowVersionId) REFERENCES workflow_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
|
||||
"workflow_review_request_workflow" {
|
||||
varchar_36_ baselineVersionId FK
|
||||
varchar_36_ id PK
|
||||
varchar_36_ workflowId FK
|
||||
varchar_36_ workflowReviewRequestId FK
|
||||
varchar_36_ workflowVersionId FK
|
||||
}
|
||||
"workflow_history" {
|
||||
varchar_255_ authors
|
||||
boolean autosaved
|
||||
TEXT connections
|
||||
datetime_3_ createdAt
|
||||
TEXT description
|
||||
varchar_128_ name
|
||||
TEXT nodeGroups
|
||||
TEXT nodes
|
||||
datetime_3_ updatedAt
|
||||
varchar_36_ versionId PK
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
"workflow_entity" {
|
||||
boolean active
|
||||
varchar_36_ activeVersionId FK
|
||||
|
|
@ -89,19 +106,6 @@ erDiagram
|
|||
datetime_3_ updatedAt
|
||||
varchar updatedById FK
|
||||
}
|
||||
"workflow_history" {
|
||||
varchar_255_ authors
|
||||
boolean autosaved
|
||||
TEXT connections
|
||||
datetime_3_ createdAt
|
||||
TEXT description
|
||||
varchar_128_ name
|
||||
TEXT nodeGroups
|
||||
TEXT nodes
|
||||
datetime_3_ updatedAt
|
||||
varchar_36_ versionId PK
|
||||
varchar_36_ workflowId FK
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export * from './instance-registry-types';
|
|||
export * from './redaction-enforcement';
|
||||
export * from './redaction-enforcement-floor';
|
||||
export * from './workflow-reviews-policy';
|
||||
export type * from './workflow-review-activity';
|
||||
export type * from './workflow-review-eligible-reviewer';
|
||||
export * from './workflow-review-request-summary';
|
||||
export * from './workflow-publish-blocked-details';
|
||||
|
|
|
|||
18
packages/@n8n/api-types/src/workflow-review-activity.ts
Normal file
18
packages/@n8n/api-types/src/workflow-review-activity.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Feed entry kinds, named `<model>.<event>`: a dot separates model from event, snake_case within a
|
||||
* multi-word event. Mirrored by a CHECK constraint on `workflow_review_activity.type`, so a new
|
||||
* kind needs a migration.
|
||||
*
|
||||
* Here rather than on the entity because it is a wire type: the frontend switches on these
|
||||
* values to pick a renderer.
|
||||
*/
|
||||
export type WorkflowReviewActivityType =
|
||||
| 'review.opened'
|
||||
| 'comment.created'
|
||||
| 'review.changes_requested'
|
||||
| 'review.version_updated'
|
||||
| 'review.approved'
|
||||
| 'workflow.published'
|
||||
/** Closed without an approval: workflow archived or deleted, or the review abandoned. An
|
||||
* approval writes `review.approved` instead, never both. */
|
||||
| 'review.closed';
|
||||
|
|
@ -72,6 +72,8 @@ import {
|
|||
} from './workflow-publication-trigger-status';
|
||||
import { WorkflowPublishHistory } from './workflow-publish-history';
|
||||
import { WorkflowPublishedVersion } from './workflow-published-version';
|
||||
import { WorkflowReviewActivityComment } from './workflow-review-activity-comment.ee';
|
||||
import { WorkflowReviewActivity } from './workflow-review-activity.ee';
|
||||
import { WorkflowReviewRequestAuthor } from './workflow-review-request-author.ee';
|
||||
import { WorkflowReviewRequestReviewer } from './workflow-review-request-reviewer.ee';
|
||||
import { WorkflowReviewRequestWorkflow } from './workflow-review-request-workflow.ee';
|
||||
|
|
@ -156,6 +158,8 @@ export {
|
|||
WorkflowReviewRequestWorkflow,
|
||||
WorkflowReviewRequestReviewer,
|
||||
WorkflowReviewRequestAuthor,
|
||||
WorkflowReviewActivity,
|
||||
WorkflowReviewActivityComment,
|
||||
ExecutionData,
|
||||
ExecutionMetadata,
|
||||
AnnotationTagEntity,
|
||||
|
|
@ -213,6 +217,8 @@ export const entities = {
|
|||
WorkflowReviewRequestWorkflow,
|
||||
WorkflowReviewRequestReviewer,
|
||||
WorkflowReviewRequestAuthor,
|
||||
WorkflowReviewActivity,
|
||||
WorkflowReviewActivityComment,
|
||||
ExecutionData,
|
||||
ExecutionMetadata,
|
||||
AnnotationTagEntity,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { Column, Entity, Index, PrimaryGeneratedColumn } from '@n8n/typeorm';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import { DateTimeColumn, JsonColumn, WithCreatedAt } from './abstract-entity';
|
||||
|
||||
/**
|
||||
* One message in a comment thread. A `type = 'comment.created'` activity entry is the thread header,
|
||||
* owning these rows; replies are messages here, not further activity entries. A header with no messages
|
||||
* cannot be rendered, having no `body` of its own, so create a thread and its first message in one
|
||||
* transaction.
|
||||
*/
|
||||
@Entity({ name: 'workflow_review_activity_comment' })
|
||||
@Index('IDX_workflow_review_activity_comment_activity', ['activityId', 'id'])
|
||||
export class WorkflowReviewActivityComment extends WithCreatedAt {
|
||||
/**
|
||||
* Orders the messages within a thread. Same SQLite id-reuse caveat as
|
||||
* `WorkflowReviewActivity.id`, scoped by `activityId`.
|
||||
*/
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Thread this message belongs to. This table carries no `workflowReviewRequestId`, and `id` is
|
||||
* globally sequential and enumerable across projects, so a by-id mutation must authorise by
|
||||
* joining comment -> activity -> request -> project.
|
||||
*/
|
||||
@Column({ type: 'int' })
|
||||
activityId: number;
|
||||
|
||||
/** Author of this message. The header's `createdById` only covers whoever opened the thread. */
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
createdById: string | null;
|
||||
|
||||
/** Only user-editable text in the feed; nulled on delete. */
|
||||
@Column({ type: 'text', nullable: true })
|
||||
body: string | null;
|
||||
|
||||
@JsonColumn({ nullable: true })
|
||||
history: IDataObject[] | null;
|
||||
|
||||
/** Intentionally not `@UpdateDateColumn`: stays null until the body is actually edited. */
|
||||
@DateTimeColumn({ nullable: true })
|
||||
updatedAt: Date | null;
|
||||
|
||||
@DateTimeColumn({ nullable: true })
|
||||
deletedAt: Date | null;
|
||||
}
|
||||
51
packages/@n8n/db/src/entities/workflow-review-activity.ee.ts
Normal file
51
packages/@n8n/db/src/entities/workflow-review-activity.ee.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import type { WorkflowReviewActivityType } from '@n8n/api-types';
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from '@n8n/typeorm';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import { JsonColumn, WithCreatedAt } from './abstract-entity';
|
||||
|
||||
/**
|
||||
* Altering this table on SQLite recreates it, and `workflow_review_activity_comment` references it
|
||||
* with ON DELETE CASCADE, so the rebuild takes the comment rows with it: any later migration
|
||||
* touching these columns needs a `sqlite/` subclass with `withFKsDisabled`.
|
||||
*/
|
||||
@Entity({ name: 'workflow_review_activity' })
|
||||
@Index('IDX_workflow_review_activity_request', ['workflowReviewRequestId', 'id'])
|
||||
export class WorkflowReviewActivity extends WithCreatedAt {
|
||||
/**
|
||||
* Autoincrement int, not the usual nanoid: the feed orders by id and pages on it as a cursor.
|
||||
* On SQLite this is a plain rowid alias, so ids are reused once the top rows are deleted, which
|
||||
* deleting a review request does. Never trust a cursor across a deletion; scope reads by
|
||||
* `workflowReviewRequestId`.
|
||||
*/
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 36 })
|
||||
workflowReviewRequestId: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64 })
|
||||
type: WorkflowReviewActivityType;
|
||||
|
||||
@Column({ type: 'int', default: 1 })
|
||||
typeVersion: number;
|
||||
|
||||
/**
|
||||
* Immutable per-type detail. Ids only: a user id stored here would survive user deletion,
|
||||
* unlike `createdById`, so a type needing another actor gets its own `SET NULL` column.
|
||||
*/
|
||||
@JsonColumn({ nullable: true })
|
||||
data: IDataObject | null;
|
||||
|
||||
/** Who produced this entry. For a comment thread, whoever opened it. */
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
createdById: string | null;
|
||||
|
||||
/**
|
||||
* Scopes an entry to one workflow; `null` for review-level entries such as comments. A column
|
||||
* rather than a key in `data` so the feed can query and filter on it directly, for instance to
|
||||
* leave out entries about workflows the reader may not open. Nothing writes it yet.
|
||||
*/
|
||||
@Column({ type: 'varchar', length: 36, nullable: true })
|
||||
workflowId: string | null;
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { Entity, PrimaryColumn } from '@n8n/typeorm';
|
||||
import { Entity, Index, PrimaryColumn } from '@n8n/typeorm';
|
||||
|
||||
@Entity({ name: 'workflow_review_request_authors' })
|
||||
// The primary key leads on `workflowReviewRequestId`, so "which reviews did I author" cannot use
|
||||
// it. Mirrors the index the reviewers table carries for the same lookup.
|
||||
@Index('IDX_workflow_review_request_authors_user', ['userId', 'workflowReviewRequestId'])
|
||||
export class WorkflowReviewRequestAuthor {
|
||||
@PrimaryColumn({ type: 'varchar', length: 36 })
|
||||
workflowReviewRequestId: string;
|
||||
|
|
|
|||
|
|
@ -20,4 +20,7 @@ export class WorkflowReviewRequestWorkflow extends WithStringId {
|
|||
|
||||
@Column({ type: 'varchar', length: 36, nullable: true })
|
||||
workflowVersionId: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 36, nullable: true })
|
||||
baselineVersionId: string | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||
|
||||
const ACTIVITY_TABLE = 'workflow_review_activity';
|
||||
const COMMENT_TABLE = 'workflow_review_activity_comment';
|
||||
|
||||
const REQUEST_TABLE = 'workflow_review_request';
|
||||
const REQUEST_WORKFLOW_TABLE = 'workflow_review_request_workflow';
|
||||
const REQUEST_AUTHORS_TABLE = 'workflow_review_request_authors';
|
||||
const WORKFLOW_TABLE = 'workflow_entity';
|
||||
const WORKFLOW_HISTORY_TABLE = 'workflow_history';
|
||||
const USER_TABLE = 'user';
|
||||
|
||||
const BASELINE_VERSION_COLUMN = 'baselineVersionId';
|
||||
// Named explicitly because SQLite drops a foreign key by matching the constraint name only:
|
||||
// an unnamed one never matches the loaded (auto-named) constraint and silently stays behind.
|
||||
const BASELINE_VERSION_FK = 'FK_workflow_review_request_workflow_baselineVersionId';
|
||||
|
||||
const ACTIVITY_TYPES = [
|
||||
'review.opened',
|
||||
'comment.created',
|
||||
'review.changes_requested',
|
||||
'review.version_updated',
|
||||
'review.approved',
|
||||
'workflow.published',
|
||||
'review.closed',
|
||||
];
|
||||
|
||||
export class CreateWorkflowReviewActivityTablesAndBaseline1785843640527
|
||||
implements ReversibleMigration
|
||||
{
|
||||
async up(context: MigrationContext) {
|
||||
await this.createActivityTable(context);
|
||||
await this.createCommentTable(context);
|
||||
await this.addBaselineVersionColumn(context);
|
||||
await this.addAuthorsUserIndex(context);
|
||||
}
|
||||
|
||||
async down(context: MigrationContext) {
|
||||
await this.dropAuthorsUserIndex(context);
|
||||
await this.dropBaselineVersionColumn(context);
|
||||
// Comment table first: its FK blocks dropping the activity table on Postgres.
|
||||
await context.schemaBuilder.dropTable(COMMENT_TABLE);
|
||||
await context.schemaBuilder.dropTable(ACTIVITY_TABLE);
|
||||
}
|
||||
|
||||
private async createActivityTable({
|
||||
schemaBuilder: { createTable, createIndex, column },
|
||||
tablePrefix,
|
||||
}: MigrationContext) {
|
||||
await createTable(ACTIVITY_TABLE)
|
||||
.withColumns(
|
||||
column('id').int.primary.autoGenerate2,
|
||||
column('workflowReviewRequestId').varchar(36).notNull,
|
||||
// Widening this list later needs a sqlite/ subclass with `withFKsDisabled = true as
|
||||
// const`: on SQLite dropEnumCheck/addEnumCheck recreate the table, and the comment
|
||||
// table's ON DELETE CASCADE FK would take every comment row with it.
|
||||
column('type')
|
||||
.varchar(64)
|
||||
.notNull.withEnumCheck(ACTIVITY_TYPES)
|
||||
.comment('Feed entry kind; see WorkflowReviewActivityType in @n8n/api-types'),
|
||||
column('typeVersion')
|
||||
.int.notNull.default(1)
|
||||
.comment('Schema version of the `data` payload for this `type`'),
|
||||
column('data').json.comment('Detail per activity type'),
|
||||
column('createdById').uuid,
|
||||
// A column rather than a key in `data` so the feed can query and filter on it
|
||||
// directly, for instance by what the reader may open.
|
||||
column('workflowId')
|
||||
.varchar(36)
|
||||
.comment('Scopes the entry to one workflow; NULL for review-level entries like comments'),
|
||||
)
|
||||
.withCreatedAt.withForeignKey('workflowReviewRequestId', {
|
||||
tableName: REQUEST_TABLE,
|
||||
columnName: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
.withForeignKey('createdById', {
|
||||
tableName: USER_TABLE,
|
||||
columnName: 'id',
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
// CASCADE, never SET NULL: NULL means "review-level", so nulling this on workflow
|
||||
// deletion would widen a scoped entry to everyone who can read the review.
|
||||
.withForeignKey('workflowId', {
|
||||
tableName: WORKFLOW_TABLE,
|
||||
columnName: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
});
|
||||
|
||||
// Feed read: all entries for one request ORDER BY id, keyset paginated on id.
|
||||
// Leading column also serves the request FK cascade.
|
||||
await createIndex(
|
||||
ACTIVITY_TABLE,
|
||||
['workflowReviewRequestId', 'id'],
|
||||
false,
|
||||
`IDX_${tablePrefix}workflow_review_activity_request`,
|
||||
);
|
||||
}
|
||||
|
||||
private async createCommentTable({
|
||||
schemaBuilder: { createTable, createIndex, column },
|
||||
tablePrefix,
|
||||
}: MigrationContext) {
|
||||
await createTable(COMMENT_TABLE)
|
||||
.withColumns(
|
||||
column('id').int.primary.autoGenerate2,
|
||||
column('activityId').int.notNull.comment(
|
||||
'Thread this message belongs to; the activity row is its header',
|
||||
),
|
||||
column('createdById').uuid,
|
||||
column('body').text.comment('Only user-editable text in the feed; nulled on delete'),
|
||||
column('history').json.comment('Comment revision history.'),
|
||||
column('updatedAt').timestampTimezone().comment('Set when the body is edited'),
|
||||
column('deletedAt').timestampTimezone().comment('Set when the comment is deleted'),
|
||||
)
|
||||
.withCreatedAt.withForeignKey('activityId', {
|
||||
tableName: ACTIVITY_TABLE,
|
||||
columnName: 'id',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
.withForeignKey('createdById', {
|
||||
tableName: USER_TABLE,
|
||||
columnName: 'id',
|
||||
onDelete: 'SET NULL',
|
||||
});
|
||||
|
||||
// Thread read: all messages for one activity row ORDER BY id.
|
||||
// Leading column also serves the activity FK cascade.
|
||||
await createIndex(
|
||||
COMMENT_TABLE,
|
||||
['activityId', 'id'],
|
||||
false,
|
||||
`IDX_${tablePrefix}workflow_review_activity_comment_activity`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rides along with the activity tables so the review feature needs a single schema change:
|
||||
* the feed records that a version was approved, this column pins which one.
|
||||
*/
|
||||
private async addBaselineVersionColumn({
|
||||
schemaBuilder: { addColumns, addForeignKey, column },
|
||||
}: MigrationContext) {
|
||||
await addColumns(
|
||||
REQUEST_WORKFLOW_TABLE,
|
||||
[
|
||||
column(BASELINE_VERSION_COLUMN)
|
||||
.varchar(36)
|
||||
.comment('Published workflow_history version captured when the review was approved'),
|
||||
],
|
||||
{ recreatesOnSqlite: true },
|
||||
);
|
||||
await addForeignKey(
|
||||
REQUEST_WORKFLOW_TABLE,
|
||||
BASELINE_VERSION_COLUMN,
|
||||
[WORKFLOW_HISTORY_TABLE, 'versionId'],
|
||||
BASELINE_VERSION_FK,
|
||||
'SET NULL',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The authors table's primary key leads on `workflowReviewRequestId`, so "which reviews did I
|
||||
* author" cannot use it. Mirrors the index the reviewers table already carries. Added here
|
||||
* rather than in its own migration because that table's own migration has already shipped.
|
||||
*/
|
||||
private async addAuthorsUserIndex({
|
||||
schemaBuilder: { createIndex },
|
||||
tablePrefix,
|
||||
}: MigrationContext) {
|
||||
await createIndex(
|
||||
REQUEST_AUTHORS_TABLE,
|
||||
['userId', 'workflowReviewRequestId'],
|
||||
false,
|
||||
`IDX_${tablePrefix}workflow_review_request_authors_user`,
|
||||
);
|
||||
}
|
||||
|
||||
private async dropAuthorsUserIndex({
|
||||
schemaBuilder: { dropIndex },
|
||||
tablePrefix,
|
||||
}: MigrationContext) {
|
||||
await dropIndex(REQUEST_AUTHORS_TABLE, ['userId', 'workflowReviewRequestId'], {
|
||||
customIndexName: `IDX_${tablePrefix}workflow_review_request_authors_user`,
|
||||
});
|
||||
}
|
||||
|
||||
private async dropBaselineVersionColumn({
|
||||
schemaBuilder: { dropColumns, dropForeignKey },
|
||||
}: MigrationContext) {
|
||||
await dropForeignKey(
|
||||
REQUEST_WORKFLOW_TABLE,
|
||||
BASELINE_VERSION_COLUMN,
|
||||
[WORKFLOW_HISTORY_TABLE, 'versionId'],
|
||||
BASELINE_VERSION_FK,
|
||||
);
|
||||
await dropColumns(REQUEST_WORKFLOW_TABLE, [BASELINE_VERSION_COLUMN], {
|
||||
recreatesOnSqlite: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
{
|
||||
"name": "WorkflowReviewRequest",
|
||||
"path": "packages/@n8n/db/src/entities/workflow-review-request.ee.ts",
|
||||
"reason": "Not transferred with the project. Open reviews are closed when their workflow is archived, moved to another project, or deleted (workflow lifecycle hooks); rows are removed via the projectId FK cascade when the source project itself is deleted, as in ownership-transfer flows."
|
||||
"reason": "Not transferred with the project. Open reviews are closed when their workflow is archived, moved to another project, or deleted (workflow lifecycle hooks); rows are removed via the projectId FK cascade when the source project itself is deleted, as in ownership-transfer flows. WorkflowReviewActivity and WorkflowReviewActivityComment ride this entity's cascade and have no projectId of their own, so they never appear in this manifest."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,595 @@
|
|||
import {
|
||||
createTestMigrationContext,
|
||||
initDbUpToMigration,
|
||||
runSingleMigration,
|
||||
undoLastSingleMigration,
|
||||
type TestMigrationContext,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { DbConnection, WorkflowReviewActivity, WorkflowReviewActivityComment } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DataSource } from '@n8n/typeorm';
|
||||
import { generateNanoId } from '@n8n/utils/generate-nano-id';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const MIGRATION_NAME = 'CreateWorkflowReviewActivityTablesAndBaseline1785843640527';
|
||||
|
||||
const ACTIVITY_TABLE = 'workflow_review_activity';
|
||||
const COMMENT_TABLE = 'workflow_review_activity_comment';
|
||||
const REQUEST_WORKFLOW_TABLE = 'workflow_review_request_workflow';
|
||||
const BASELINE_VERSION_COLUMN = 'baselineVersionId';
|
||||
const BASELINE_VERSION_FK = 'FK_workflow_review_request_workflow_baselineVersionId';
|
||||
const AUTHORS_TABLE = 'workflow_review_request_authors';
|
||||
const AUTHORS_USER_INDEX_SUFFIX = 'workflow_review_request_authors_user';
|
||||
|
||||
async function tableExists(context: TestMigrationContext, table: string): Promise<boolean> {
|
||||
const name = `${context.tablePrefix}${table}`;
|
||||
if (context.isSqlite) {
|
||||
const rows = await context.runQuery<Array<{ name: string }>>(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = :name",
|
||||
{ name },
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
const rows = await context.runQuery<Array<{ tablename: string }>>(
|
||||
'SELECT tablename FROM pg_tables WHERE tablename = :name',
|
||||
{ name },
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async function columnMeta(
|
||||
context: TestMigrationContext,
|
||||
table: string,
|
||||
columnName: string,
|
||||
): Promise<{ nullable: boolean } | undefined> {
|
||||
if (context.isSqlite) {
|
||||
const rows = await context.runQuery<Array<{ name: string; notnull: number }>>(
|
||||
`PRAGMA table_info(${context.escape.tableName(table)})`,
|
||||
);
|
||||
const row = rows.find(({ name }) => name === columnName);
|
||||
return row && { nullable: Number(row.notnull) === 0 };
|
||||
}
|
||||
const rows = await context.runQuery<Array<{ is_nullable: string }>>(
|
||||
`SELECT is_nullable FROM information_schema.columns
|
||||
WHERE table_name = :tableName AND column_name = :columnName`,
|
||||
{ tableName: `${context.tablePrefix}${table}`, columnName },
|
||||
);
|
||||
return rows[0] && { nullable: rows[0].is_nullable === 'YES' };
|
||||
}
|
||||
|
||||
async function indexExists(
|
||||
context: TestMigrationContext,
|
||||
table: string,
|
||||
indexNameSuffix: string,
|
||||
): Promise<boolean> {
|
||||
// The DSL composes a custom index name as `IDX_<prefix><name>`, prefix in the middle, so a
|
||||
// plain `<prefix><name>` misses it on the Postgres leg, which runs with DB_TABLE_PREFIX.
|
||||
const name = `IDX_${context.tablePrefix}${indexNameSuffix}`;
|
||||
if (context.isSqlite) {
|
||||
const rows = await context.runQuery<Array<{ name: string }>>(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'index' AND name = :name",
|
||||
{ name },
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
const rows = await context.runQuery<Array<{ indexname: string }>>(
|
||||
'SELECT indexname FROM pg_indexes WHERE tablename = :tableName AND indexname = :name',
|
||||
{ tableName: `${context.tablePrefix}${table}`, name },
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async function foreignKeyNames(
|
||||
context: TestMigrationContext,
|
||||
table: string,
|
||||
columnName: string,
|
||||
): Promise<string[]> {
|
||||
if (context.isSqlite) {
|
||||
const [{ sql }] = await context.runQuery<Array<{ sql: string }>>(
|
||||
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = :name",
|
||||
{ name: `${context.tablePrefix}${table}` },
|
||||
);
|
||||
const pattern = new RegExp(`CONSTRAINT "([^"]+)" FOREIGN KEY \\("${columnName}"\\)`, 'g');
|
||||
return [...sql.matchAll(pattern)].map(([, name]) => name);
|
||||
}
|
||||
const rows = await context.runQuery<Array<{ constraintName: string }>>(
|
||||
`SELECT tc.constraint_name AS ${context.escape.columnName('constraintName')}
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
||||
WHERE tc.table_name = :tableName
|
||||
AND tc.constraint_type = 'FOREIGN KEY'
|
||||
AND kcu.column_name = :columnName`,
|
||||
{ tableName: `${context.tablePrefix}${table}`, columnName },
|
||||
);
|
||||
return rows.map((row) => row.constraintName);
|
||||
}
|
||||
|
||||
describe('CreateWorkflowReviewActivityTablesAndBaseline Migration', () => {
|
||||
let dataSource: DataSource;
|
||||
/** Seeded before the migration runs, so the column add has existing data to preserve. */
|
||||
let preExistingLinkRowId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const dbConnection = Container.get(DbConnection);
|
||||
await dbConnection.init();
|
||||
dataSource = Container.get(DataSource);
|
||||
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
await context.queryRunner.clearDatabase();
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
|
||||
await initDbUpToMigration(MIGRATION_NAME);
|
||||
|
||||
const seedContext = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
preExistingLinkRowId = await seedLinkedWorkflowRow(seedContext);
|
||||
} finally {
|
||||
await seedContext.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Container.get(DbConnection).close();
|
||||
});
|
||||
|
||||
async function seedUser(context: TestMigrationContext): Promise<string> {
|
||||
const userId = randomUUID();
|
||||
const now = new Date();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('user')} ("id", "email", "firstName", "lastName", "password", "createdAt", "updatedAt")
|
||||
VALUES (:id, :email, :firstName, :lastName, :password, :createdAt, :updatedAt)`,
|
||||
{
|
||||
id: userId,
|
||||
email: `${userId}@example.com`,
|
||||
firstName: 'Activity',
|
||||
lastName: 'Tester',
|
||||
password: 'hashed',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
);
|
||||
return userId;
|
||||
}
|
||||
|
||||
async function seedRequest(context: TestMigrationContext): Promise<string> {
|
||||
const projectId = generateNanoId();
|
||||
const requestId = generateNanoId();
|
||||
const now = new Date();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('project')} ("id", "name", "type", "createdAt", "updatedAt")
|
||||
VALUES (:id, :name, :type, :createdAt, :updatedAt)`,
|
||||
{
|
||||
id: projectId,
|
||||
name: `Activity test project ${projectId}`,
|
||||
type: 'team',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
);
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('workflow_review_request')}
|
||||
("id", "projectId", "state", "decision", "title", "createdAt", "updatedAt")
|
||||
VALUES (:id, :projectId, :state, :decision, :title, :createdAt, :updatedAt)`,
|
||||
{
|
||||
id: requestId,
|
||||
projectId,
|
||||
state: 'open',
|
||||
decision: 'pending',
|
||||
title: 'Activity test review',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
);
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an activity row without an `id` and reads the assigned one back as the table max.
|
||||
* Safe because this suite runs single-fork with no file parallelism. `INSERT ... RETURNING`
|
||||
* is not portable here: the SQLite pooled driver replaces the result of any INSERT with
|
||||
* `lastID`, where Postgres returns rows.
|
||||
*/
|
||||
async function insertActivity(
|
||||
context: TestMigrationContext,
|
||||
fields: {
|
||||
workflowReviewRequestId: string;
|
||||
type?: string;
|
||||
createdById?: string;
|
||||
workflowId?: string;
|
||||
},
|
||||
): Promise<number> {
|
||||
const activityTable = context.escape.tableName(ACTIVITY_TABLE);
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${activityTable} ("workflowReviewRequestId", "type", "createdById", "workflowId", "createdAt")
|
||||
VALUES (:workflowReviewRequestId, :type, :createdById, :workflowId, :createdAt)`,
|
||||
{
|
||||
workflowReviewRequestId: fields.workflowReviewRequestId,
|
||||
type: fields.type ?? 'review.opened',
|
||||
createdById: fields.createdById ?? null,
|
||||
workflowId: fields.workflowId ?? null,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
);
|
||||
return await maxActivityId(context);
|
||||
}
|
||||
|
||||
async function maxActivityId(context: TestMigrationContext): Promise<number> {
|
||||
const [{ maxId }] = await context.runQuery<Array<{ maxId: number | null }>>(
|
||||
`SELECT MAX("id") AS "maxId" FROM ${context.escape.tableName(ACTIVITY_TABLE)}`,
|
||||
);
|
||||
return maxId ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `workflow_review_request_workflow` row in its pre-migration shape. Adding a column to that
|
||||
* table recreates it on SQLite, so this is the data the recreate has to carry across.
|
||||
*/
|
||||
async function seedLinkedWorkflowRow(context: TestMigrationContext): Promise<string> {
|
||||
const rowId = generateNanoId();
|
||||
const requestId = await seedRequest(context);
|
||||
const workflowId = await seedWorkflow(context);
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName(REQUEST_WORKFLOW_TABLE)}
|
||||
("id", "workflowReviewRequestId", "workflowId", "workflowVersionId")
|
||||
VALUES (:id, :requestId, :workflowId, :versionId)`,
|
||||
{ id: rowId, requestId, workflowId, versionId: null },
|
||||
);
|
||||
return rowId;
|
||||
}
|
||||
|
||||
async function seedWorkflow(context: TestMigrationContext): Promise<string> {
|
||||
const workflowId = generateNanoId();
|
||||
const now = new Date();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('workflow_entity')}
|
||||
("id", "name", "active", "nodes", "connections", "versionId", "createdAt", "updatedAt")
|
||||
VALUES (:id, :name, :active, :nodes, :connections, :versionId, :createdAt, :updatedAt)`,
|
||||
{
|
||||
id: workflowId,
|
||||
name: `Activity test workflow ${workflowId}`,
|
||||
active: false,
|
||||
nodes: '[]',
|
||||
connections: '{}',
|
||||
versionId: randomUUID(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
);
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
async function insertComment(
|
||||
context: TestMigrationContext,
|
||||
activityId: number | null,
|
||||
createdById?: string,
|
||||
): Promise<void> {
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName(COMMENT_TABLE)} ("activityId", "createdById", "body", "createdAt")
|
||||
VALUES (:activityId, :createdById, :body, :createdAt)`,
|
||||
{
|
||||
activityId,
|
||||
createdById: createdById ?? null,
|
||||
body: 'Comment body',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe('up', () => {
|
||||
beforeAll(async () => {
|
||||
await runSingleMigration(MIGRATION_NAME);
|
||||
});
|
||||
|
||||
it('round-trips an activity and its comment thread through the entities', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
let requestId: string;
|
||||
let userId: string;
|
||||
try {
|
||||
requestId = await seedRequest(context);
|
||||
userId = await seedUser(context);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
|
||||
const activityRepository = dataSource.getRepository(WorkflowReviewActivity);
|
||||
const commentRepository = dataSource.getRepository(WorkflowReviewActivityComment);
|
||||
|
||||
const data = { workflowId: 'wf-1', versionId: 'v-1' };
|
||||
const history = [{ body: 'First draft', editedAt: '2026-01-02T03:04:05.678Z' }];
|
||||
const editedAt = new Date('2026-01-02T03:04:05.678Z');
|
||||
const savedSubmission = await activityRepository.save(
|
||||
activityRepository.create({
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'review.opened',
|
||||
data,
|
||||
}),
|
||||
);
|
||||
const savedThread = await activityRepository.save(
|
||||
activityRepository.create({ workflowReviewRequestId: requestId, type: 'comment.created' }),
|
||||
);
|
||||
const savedMessage = await commentRepository.save(
|
||||
commentRepository.create({
|
||||
activityId: savedThread.id,
|
||||
body: 'Looks good',
|
||||
history,
|
||||
updatedAt: editedAt,
|
||||
}),
|
||||
);
|
||||
const savedReply = await commentRepository.save(
|
||||
commentRepository.create({
|
||||
activityId: savedThread.id,
|
||||
body: 'Agreed',
|
||||
createdById: userId,
|
||||
}),
|
||||
);
|
||||
|
||||
// Assert on freshly loaded rows only: what `save()` returns is partly the in-memory
|
||||
// object, so unset columns read back as `undefined` and `data` is the very object passed in.
|
||||
const submission = await activityRepository.findOneByOrFail({ id: savedSubmission.id });
|
||||
const thread = await activityRepository.findOneByOrFail({ id: savedThread.id });
|
||||
const message = await commentRepository.findOneByOrFail({ id: savedMessage.id });
|
||||
const reply = await commentRepository.findOneByOrFail({ id: savedReply.id });
|
||||
|
||||
expect(typeof submission.id).toBe('number');
|
||||
expect(thread.id).toBeGreaterThan(submission.id);
|
||||
expect(submission.type).toBe('review.opened');
|
||||
expect(submission.typeVersion).toBe(1);
|
||||
expect(submission.data).toEqual(data);
|
||||
expect(submission.createdById).toBeNull();
|
||||
expect(submission.createdAt).toBeInstanceOf(Date);
|
||||
expect(thread.data).toBeNull();
|
||||
|
||||
// One activity row owns many message rows, ordered by their own id.
|
||||
expect(message.activityId).toBe(thread.id);
|
||||
expect(reply.activityId).toBe(thread.id);
|
||||
expect(reply.id).toBeGreaterThan(message.id);
|
||||
|
||||
expect(message.body).toBe('Looks good');
|
||||
expect(message.history).toEqual(history);
|
||||
expect(message.updatedAt).toEqual(editedAt);
|
||||
expect(message.deletedAt).toBeNull();
|
||||
expect(message.createdAt).toBeInstanceOf(Date);
|
||||
expect(message.createdById).toBeNull();
|
||||
expect(reply.createdById).toBe(userId);
|
||||
});
|
||||
|
||||
it('rejects a comment with no activityId and an activity with an unknown type', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
// SQLite says "NOT NULL constraint failed", Postgres "violates not-null constraint".
|
||||
await expect(insertComment(context, null)).rejects.toThrow(/not[- ]null constraint/i);
|
||||
|
||||
// SQLite says "CHECK constraint failed", Postgres "violates check constraint".
|
||||
const requestId = await seedRequest(context);
|
||||
await expect(
|
||||
insertActivity(context, { workflowReviewRequestId: requestId, type: 'nonsense' }),
|
||||
).rejects.toThrow(/check constraint/i);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('removes activity and comment rows when the review request is deleted', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
const requestId = await seedRequest(context);
|
||||
const activityId = await insertActivity(context, {
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'comment.created',
|
||||
});
|
||||
await insertComment(context, activityId);
|
||||
|
||||
await context.runQuery(
|
||||
`DELETE FROM ${context.escape.tableName('workflow_review_request')} WHERE "id" = :id`,
|
||||
{ id: requestId },
|
||||
);
|
||||
|
||||
// Both hops of request -> activity -> comment.
|
||||
const activityRows = await context.runQuery<unknown[]>(
|
||||
`SELECT "id" FROM ${context.escape.tableName(ACTIVITY_TABLE)} WHERE "id" = :id`,
|
||||
{ id: activityId },
|
||||
);
|
||||
const commentRows = await context.runQuery<unknown[]>(
|
||||
`SELECT "id" FROM ${context.escape.tableName(COMMENT_TABLE)} WHERE "activityId" = :id`,
|
||||
{ id: activityId },
|
||||
);
|
||||
expect(activityRows).toHaveLength(0);
|
||||
expect(commentRows).toHaveLength(0);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('removes every message in a thread when its activity row is deleted', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
const requestId = await seedRequest(context);
|
||||
const activityId = await insertActivity(context, {
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'comment.created',
|
||||
});
|
||||
await insertComment(context, activityId);
|
||||
await insertComment(context, activityId);
|
||||
|
||||
const commentsQuery = `SELECT "id" FROM ${context.escape.tableName(COMMENT_TABLE)} WHERE "activityId" = :id`;
|
||||
expect(await context.runQuery<unknown[]>(commentsQuery, { id: activityId })).toHaveLength(
|
||||
2,
|
||||
);
|
||||
|
||||
await context.runQuery(
|
||||
`DELETE FROM ${context.escape.tableName(ACTIVITY_TABLE)} WHERE "id" = :id`,
|
||||
{ id: activityId },
|
||||
);
|
||||
|
||||
expect(await context.runQuery<unknown[]>(commentsQuery, { id: activityId })).toHaveLength(
|
||||
0,
|
||||
);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps activity and comment rows and nulls createdById when the author is deleted', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
const requestId = await seedRequest(context);
|
||||
const userId = await seedUser(context);
|
||||
const activityId = await insertActivity(context, {
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'comment.created',
|
||||
createdById: userId,
|
||||
});
|
||||
await insertComment(context, activityId, userId);
|
||||
|
||||
await context.runQuery(`DELETE FROM ${context.escape.tableName('user')} WHERE "id" = :id`, {
|
||||
id: userId,
|
||||
});
|
||||
|
||||
const activityRows = await context.runQuery<Array<{ createdById: string | null }>>(
|
||||
`SELECT "createdById" FROM ${context.escape.tableName(ACTIVITY_TABLE)} WHERE "id" = :id`,
|
||||
{ id: activityId },
|
||||
);
|
||||
const commentRows = await context.runQuery<Array<{ createdById: string | null }>>(
|
||||
`SELECT "createdById" FROM ${context.escape.tableName(COMMENT_TABLE)} WHERE "activityId" = :id`,
|
||||
{ id: activityId },
|
||||
);
|
||||
expect(activityRows).toEqual([{ createdById: null }]);
|
||||
expect(commentRows).toEqual([{ createdById: null }]);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps rows that already existed in the table it adds the column to', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
const rows = await context.runQuery<Array<{ baselineVersionId: string | null }>>(
|
||||
`SELECT "baselineVersionId" FROM ${context.escape.tableName(REQUEST_WORKFLOW_TABLE)} WHERE "id" = :id`,
|
||||
{ id: preExistingLinkRowId },
|
||||
);
|
||||
expect(rows).toEqual([{ baselineVersionId: null }]);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('adds baselineVersionId as nullable so reviews closed before this migration stay valid', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
expect(await columnMeta(context, REQUEST_WORKFLOW_TABLE, BASELINE_VERSION_COLUMN)).toEqual({
|
||||
nullable: true,
|
||||
});
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
// The whole reason `workflowId` is CASCADE and not SET NULL: NULL marks a review-level
|
||||
// entry, so nulling on delete would widen a workflow-scoped entry to everyone who can
|
||||
// read the review.
|
||||
it('drops entries about a deleted workflow and keeps the review-level ones', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
const requestId = await seedRequest(context);
|
||||
const workflowId = await seedWorkflow(context);
|
||||
const scopedId = await insertActivity(context, {
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'workflow.published',
|
||||
workflowId,
|
||||
});
|
||||
const reviewLevelId = await insertActivity(context, {
|
||||
workflowReviewRequestId: requestId,
|
||||
type: 'comment.created',
|
||||
});
|
||||
await insertComment(context, reviewLevelId);
|
||||
|
||||
await context.runQuery(
|
||||
`DELETE FROM ${context.escape.tableName('workflow_entity')} WHERE "id" = :id`,
|
||||
{ id: workflowId },
|
||||
);
|
||||
|
||||
const remaining = await context.runQuery<Array<{ id: number }>>(
|
||||
`SELECT "id" FROM ${context.escape.tableName(ACTIVITY_TABLE)} WHERE "workflowReviewRequestId" = :requestId`,
|
||||
{ requestId },
|
||||
);
|
||||
expect(remaining.map(({ id }) => Number(id))).toEqual([reviewLevelId]);
|
||||
expect(remaining.map(({ id }) => Number(id))).not.toContain(scopedId);
|
||||
|
||||
// The comment on the surviving entry rides its parent, so it stays too.
|
||||
const commentRows = await context.runQuery<unknown[]>(
|
||||
`SELECT "id" FROM ${context.escape.tableName(COMMENT_TABLE)} WHERE "activityId" = :id`,
|
||||
{ id: reviewLevelId },
|
||||
);
|
||||
expect(commentRows).toHaveLength(1);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
// The authors table ships in an earlier migration, so this index is created here and has
|
||||
// to be dropped explicitly in `down()`; nothing else removes it.
|
||||
it('indexes the authors table by user', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
expect(await indexExists(context, AUTHORS_TABLE, AUTHORS_USER_INDEX_SUFFIX)).toBe(true);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
|
||||
// The name is what makes `down()` deterministic: SQLite drops a foreign key by matching the
|
||||
// constraint name, and an auto-generated one never matches what the DSL passes in.
|
||||
it('names the baseline foreign key explicitly', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
expect(
|
||||
await foreignKeyNames(context, REQUEST_WORKFLOW_TABLE, BASELINE_VERSION_COLUMN),
|
||||
).toEqual([BASELINE_VERSION_FK]);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('down', () => {
|
||||
// The drop order only matters on Postgres: the SQLite query runner disables foreign keys
|
||||
// for every migration run, so a wrong order would still pass on that leg.
|
||||
it('drops both tables and the baseline column', async () => {
|
||||
// Assert the precondition inside this test, not in the `up` block: under a filtered run
|
||||
// (`vitest -t`) the `up` hook is skipped, so without this the test would revert whatever
|
||||
// migration preceded ours and pass by finding tables that never existed.
|
||||
const before = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
expect(await tableExists(before, ACTIVITY_TABLE)).toBe(true);
|
||||
expect(await tableExists(before, COMMENT_TABLE)).toBe(true);
|
||||
expect(
|
||||
await columnMeta(before, REQUEST_WORKFLOW_TABLE, BASELINE_VERSION_COLUMN),
|
||||
).toBeDefined();
|
||||
expect(await indexExists(before, AUTHORS_TABLE, AUTHORS_USER_INDEX_SUFFIX)).toBe(true);
|
||||
} finally {
|
||||
// Release before reverting: holding a pooled lease across the revert exhausts the
|
||||
// Postgres pool and the migration times out trying to connect.
|
||||
await before.queryRunner.release();
|
||||
}
|
||||
|
||||
await undoLastSingleMigration();
|
||||
|
||||
const after = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
expect(await tableExists(after, ACTIVITY_TABLE)).toBe(false);
|
||||
expect(await tableExists(after, COMMENT_TABLE)).toBe(false);
|
||||
expect(
|
||||
await columnMeta(after, REQUEST_WORKFLOW_TABLE, BASELINE_VERSION_COLUMN),
|
||||
).toBeUndefined();
|
||||
// The authors table survives the revert, so a missed dropIndex would leave this behind.
|
||||
expect(await indexExists(after, AUTHORS_TABLE, AUTHORS_USER_INDEX_SUFFIX)).toBe(false);
|
||||
} finally {
|
||||
await after.queryRunner.release();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user