Compare commits

...

1 Commits

Author SHA1 Message Date
HugoCasa
2b31b4360d feat(native-triggers): add summary field to native triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:22:00 +01:00
18 changed files with 104 additions and 51 deletions

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE native_trigger\n SET script_path = $1, is_flow = $2, webhook_token_prefix = $3, service_config = $4, error = NULL, updated_at = NOW()\n WHERE\n workspace_id = $5\n AND service_name = $6\n AND external_id = $7\n ",
"query": "\n UPDATE native_trigger\n SET script_path = $1, is_flow = $2, webhook_token_prefix = $3, service_config = $4, summary = $8, error = NULL, updated_at = NOW()\n WHERE\n workspace_id = $5\n AND service_name = $6\n AND external_id = $7\n ",
"describe": {
"columns": [],
"parameters": {
@@ -21,10 +21,11 @@
}
}
},
"Text"
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "27ada97cb533c8595f1d73987c7823d8e54c96889e06895c57cafae9ca27bf8b"
"hash": "0eea6bb9d8d77fafbb4a0132a7b977bd37760e82efca322149814ad65a5be2bb"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n external_id,\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config,\n error,\n created_at,\n updated_at\n FROM\n native_trigger\n WHERE\n workspace_id = $1\n AND service_name = $2\n AND script_path = $3\n AND is_flow = $4\n LIMIT 1\n ",
"query": "\n SELECT\n external_id,\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config,\n summary,\n error,\n created_at,\n updated_at\n FROM\n native_trigger\n WHERE\n workspace_id = $1\n AND service_name = $2\n AND script_path = $3\n AND is_flow = $4\n LIMIT 1\n ",
"describe": {
"columns": [
{
@@ -50,16 +50,21 @@
},
{
"ordinal": 7,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "updated_at",
"type_info": "Timestamptz"
}
@@ -91,9 +96,10 @@
false,
true,
true,
true,
false,
false
]
},
"hash": "ee537def1ead8bee48bb9f5c1f57d42e7add6011c34d91761ba23e2c74c4032c"
"hash": "1d1d70387f6da3181d2547146cd4897608932a6c0ff9bb9a2698c0d30c039665"
}

View File

@@ -15,7 +15,7 @@
]
},
"nullable": [
false
true
]
},
"hash": "2d6607b3c38fe72b5663c32de58dacbabed4c5ae28101e3ae2694f96fd055a91"

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM variable WHERE path = $1 AND workspace_id = $2 RETURNING path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "3317484a9c09c07c2c9db9debaecc4a4d518093ab48e79365dbb808068e0b8ff"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO native_trigger (\n external_id,\n workspace_id,\n service_name,\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n ON CONFLICT (external_id, workspace_id, service_name)\n DO UPDATE SET script_path = $4, is_flow = $5, webhook_token_prefix = $6, service_config = $7, error = NULL, updated_at = NOW()\n ",
"query": "\n INSERT INTO native_trigger (\n external_id,\n workspace_id,\n service_name,\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config,\n summary\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )\n ON CONFLICT (external_id, workspace_id, service_name)\n DO UPDATE SET script_path = $4, is_flow = $5, webhook_token_prefix = $6, service_config = $7, summary = $8, error = NULL, updated_at = NOW()\n ",
"describe": {
"columns": [],
"parameters": {
@@ -21,10 +21,11 @@
"Varchar",
"Bool",
"Varchar",
"Jsonb"
"Jsonb",
"Varchar"
]
},
"nullable": []
},
"hash": "023cdbc77ea9e2c17a1aa92a5b9001f29e58e81b3f782887db6e0a627dd8ad75"
"hash": "71327558c8f5f48f9da5d2585db9e4702ed7eddef41dbf0bb1bf66b5b567d481"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n external_id,\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config,\n error,\n created_at,\n updated_at\n FROM\n native_trigger\n WHERE\n workspace_id = $1\n AND service_name = $2\n AND external_id = $3\n ",
"query": "\n SELECT\n external_id,\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n script_path,\n is_flow,\n webhook_token_prefix,\n service_config,\n summary,\n error,\n created_at,\n updated_at\n FROM\n native_trigger\n WHERE\n workspace_id = $1\n AND service_name = $2\n AND external_id = $3\n ",
"describe": {
"columns": [
{
@@ -50,16 +50,21 @@
},
{
"ordinal": 7,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "updated_at",
"type_info": "Timestamptz"
}
@@ -90,9 +95,10 @@
false,
true,
true,
true,
false,
false
]
},
"hash": "b615d73ddb43e9d655b86a0cf98f892bf40e629ee11ee4845199481755f2789d"
"hash": "88b27601cb7af37e3ddf79d308dbb2f6edb4c7c4214ee1e4ee0731e5175ed74b"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n nt.external_id,\n nt.workspace_id,\n nt.service_name AS \"service_name!: ServiceName\",\n nt.script_path,\n nt.is_flow,\n nt.webhook_token_prefix,\n nt.service_config,\n nt.error,\n nt.created_at,\n nt.updated_at\n FROM\n native_trigger nt\n WHERE\n nt.workspace_id = $1 AND\n nt.service_name = $2 AND\n ($5::text IS NULL OR nt.script_path = $5) AND\n ($6::bool IS NULL OR nt.is_flow = $6) AND\n (\n (nt.is_flow = false AND EXISTS (\n SELECT 1 FROM script s\n WHERE s.workspace_id = nt.workspace_id\n AND s.path = nt.script_path\n ))\n OR\n (nt.is_flow = true AND EXISTS (\n SELECT 1 FROM flow f\n WHERE f.workspace_id = nt.workspace_id\n AND f.path = nt.script_path\n ))\n )\n LIMIT $3\n OFFSET $4\n ",
"query": "\n SELECT\n nt.external_id,\n nt.workspace_id,\n nt.service_name AS \"service_name!: ServiceName\",\n nt.script_path,\n nt.is_flow,\n nt.webhook_token_prefix,\n nt.service_config,\n nt.summary,\n nt.error,\n nt.created_at,\n nt.updated_at\n FROM\n native_trigger nt\n WHERE\n nt.workspace_id = $1 AND\n nt.service_name = $2 AND\n ($5::text IS NULL OR nt.script_path = $5) AND\n ($6::bool IS NULL OR nt.is_flow = $6) AND\n (\n (nt.is_flow = false AND EXISTS (\n SELECT 1 FROM script s\n WHERE s.workspace_id = nt.workspace_id\n AND s.path = nt.script_path\n ))\n OR\n (nt.is_flow = true AND EXISTS (\n SELECT 1 FROM flow f\n WHERE f.workspace_id = nt.workspace_id\n AND f.path = nt.script_path\n ))\n )\n LIMIT $3\n OFFSET $4\n ",
"describe": {
"columns": [
{
@@ -50,16 +50,21 @@
},
{
"ordinal": 7,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 8,
"ordinal": 9,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"ordinal": 10,
"name": "updated_at",
"type_info": "Timestamptz"
}
@@ -93,9 +98,10 @@
false,
true,
true,
true,
false,
false
]
},
"hash": "ecab1af12a7afa685c056b9d0e526275203fc8ecddf83ca6d05c9fb77e46e7ee"
"hash": "bab3d5e34abc8e998ea9cf721da1c08cc01c313543c3bff90d7f41a4c51bcd5e"
}

View File

@@ -15,7 +15,7 @@
]
},
"nullable": [
false
true
]
},
"hash": "eba16eb819e2644284fb073c891706d78a6f24cb0e614d7d81ba1b643805bf06"

View File

@@ -0,0 +1 @@
ALTER TABLE native_trigger DROP COLUMN summary;

View File

@@ -0,0 +1 @@
ALTER TABLE native_trigger ADD COLUMN summary VARCHAR(512);

View File

@@ -422,6 +422,7 @@ async fn test_delete_integration_full_cascade(db: Pool<Postgres>) -> anyhow::Res
"ext-1",
&trigger_config,
json!({"triggerType": "drive"}),
None,
)
.await?;
@@ -511,6 +512,7 @@ async fn test_cleanup_preserves_triggers(db: Pool<Postgres>) -> anyhow::Result<(
"ext-1",
&trigger_config,
json!({"triggerType": "drive"}),
None,
)
.await?;

View File

@@ -24326,6 +24326,10 @@ components:
type: object
description: Configuration for the trigger including event_type and service_config
additionalProperties: true
summary:
type: string
nullable: true
description: Short summary describing the purpose of this trigger
error:
type: string
nullable: true
@@ -24360,6 +24364,10 @@ components:
type: object
description: Configuration for the trigger including event_type and service_config
additionalProperties: true
summary:
type: string
nullable: true
description: Short summary describing the purpose of this trigger
error:
type: string
nullable: true
@@ -24456,6 +24464,10 @@ components:
type: object
description: Service-specific configuration (e.g., event types, filters)
additionalProperties: true
summary:
type: string
nullable: true
description: Short summary describing the purpose of this trigger
required:
- script_path
- is_flow

View File

@@ -181,6 +181,7 @@ async fn create_native_trigger<T: External>(
&external_id,
&config,
service_config,
data.summary.as_deref(),
)
.await?;
@@ -287,6 +288,7 @@ async fn update_native_trigger_handler<T: External>(
&external_id,
&config,
service_config,
data.summary.as_deref(),
)
.await?;

View File

@@ -192,6 +192,7 @@ pub struct NativeTrigger {
pub is_flow: bool,
pub webhook_token_prefix: String,
pub service_config: Option<serde_json::Value>,
pub summary: Option<String>,
pub error: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -209,6 +210,7 @@ pub struct NativeTriggerData<C> {
pub script_path: String,
pub is_flow: bool,
pub service_config: C,
pub summary: Option<String>,
}
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
@@ -777,6 +779,7 @@ pub async fn store_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres>
external_id: &str,
config: &NativeTriggerConfig,
service_config: C,
summary: Option<&str>,
) -> Result<()> {
// Store only the first 10 characters of the webhook token as a prefix
let webhook_token_prefix: String = config.webhook_token.chars().take(10).collect();
@@ -790,12 +793,13 @@ pub async fn store_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres>
script_path,
is_flow,
webhook_token_prefix,
service_config
service_config,
summary
) VALUES (
$1, $2, $3, $4, $5, $6, $7
$1, $2, $3, $4, $5, $6, $7, $8
)
ON CONFLICT (external_id, workspace_id, service_name)
DO UPDATE SET script_path = $4, is_flow = $5, webhook_token_prefix = $6, service_config = $7, error = NULL, updated_at = NOW()
DO UPDATE SET script_path = $4, is_flow = $5, webhook_token_prefix = $6, service_config = $7, summary = $8, error = NULL, updated_at = NOW()
"#,
external_id,
workspace_id,
@@ -804,6 +808,7 @@ pub async fn store_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres>
config.is_flow,
webhook_token_prefix,
sqlx::types::Json(service_config) as _,
summary,
)
.execute(db)
.await?;
@@ -818,6 +823,7 @@ pub async fn update_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres
external_id: &str,
config: &NativeTriggerConfig,
service_config: Option<&RawValue>,
summary: Option<&str>,
) -> Result<()> {
// Store only the first 10 characters of the webhook token as a prefix
let webhook_token_prefix: String = config.webhook_token.chars().take(10).collect();
@@ -825,7 +831,7 @@ pub async fn update_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres
sqlx::query!(
r#"
UPDATE native_trigger
SET script_path = $1, is_flow = $2, webhook_token_prefix = $3, service_config = $4, error = NULL, updated_at = NOW()
SET script_path = $1, is_flow = $2, webhook_token_prefix = $3, service_config = $4, summary = $8, error = NULL, updated_at = NOW()
WHERE
workspace_id = $5
AND service_name = $6
@@ -838,6 +844,7 @@ pub async fn update_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres
workspace_id,
service_name as ServiceName,
external_id,
summary,
)
.execute(db)
.await?;
@@ -886,6 +893,7 @@ pub async fn get_native_trigger<'c, E: sqlx::Executor<'c, Database = Postgres>>(
is_flow,
webhook_token_prefix,
service_config,
summary,
error,
created_at,
updated_at
@@ -924,6 +932,7 @@ pub async fn get_native_trigger_by_script<'c, E: sqlx::Executor<'c, Database = P
is_flow,
webhook_token_prefix,
service_config,
summary,
error,
created_at,
updated_at
@@ -970,6 +979,7 @@ pub async fn list_native_triggers<'c, E: sqlx::Executor<'c, Database = Postgres>
nt.is_flow,
nt.webhook_token_prefix,
nt.service_config,
nt.summary,
nt.error,
nt.created_at,
nt.updated_at

View File

@@ -230,6 +230,7 @@ export async function pushNativeTrigger(
script_path: result.script_path,
is_flow: result.is_flow,
service_config: result.service_config,
summary: result.summary,
error: result.error,
};
log.debug(`Native trigger ${serviceName}/${externalId} exists on remote`);
@@ -243,6 +244,7 @@ export async function pushNativeTrigger(
script_path: localTrigger.script_path,
is_flow: localTrigger.is_flow,
service_config: localTrigger.service_config,
summary: localTrigger.summary || undefined,
};
if (remoteTrigger) {
@@ -251,11 +253,13 @@ export async function pushNativeTrigger(
script_path: localTrigger.script_path,
is_flow: localTrigger.is_flow,
service_config: localTrigger.service_config,
summary: localTrigger.summary,
};
const remoteCompare = {
script_path: remoteTrigger.script_path,
is_flow: remoteTrigger.is_flow,
service_config: remoteTrigger.service_config,
summary: remoteTrigger.summary,
};
if (isSuperset(localCompare, remoteCompare)) {

View File

@@ -16,6 +16,7 @@
import { Loader2, Save } from 'lucide-svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Section from '$lib/components/Section.svelte'
import Label from '$lib/components/Label.svelte'
import Required from '$lib/components/Required.svelte'
import NextcloudTriggerForm from './services/nextcloud/NextcloudTriggerForm.svelte'
import GoogleTriggerForm from './services/google/GoogleTriggerForm.svelte'
@@ -96,6 +97,7 @@
let isFlow = $state(false)
let externalId = $state<string | null>(null)
let can_write = $state(true)
let summary = $state('')
let originalConfig = $state<Record<string, any> | undefined>(undefined)
let initialConfig = $state<Record<string, any> | undefined>(undefined)
@@ -121,6 +123,7 @@
loadingConfig = false
loadingForm = false
can_write = true
summary = ''
originalConfig = undefined
initialConfig = undefined
}
@@ -144,6 +147,7 @@
loadingConfig = false
loadingForm = false
can_write = true
summary = nativeTrigger.summary ?? ''
originalConfig = undefined
initialConfig = undefined
}
@@ -182,6 +186,7 @@
scriptPath = fullTrigger.script_path
initialScriptPath = fullTrigger.script_path
can_write = canWrite(fullTrigger.script_path, {}, $userStore)
summary = fullTrigger.summary ?? ''
externalData = fullTrigger.external_data
// Apply default values if provided (for draft triggers)
@@ -203,7 +208,8 @@
return {
script_path: scriptPath,
is_flow: isFlow,
service_config: serviceConfig
service_config: serviceConfig,
summary: summary || undefined
}
}
@@ -422,6 +428,18 @@
</Section>
{/if}
<Section label="Metadata">
<Label label="Summary">
<input
type="text"
placeholder="Short summary to be displayed when listed"
class="text-sm w-full"
bind:value={summary}
disabled={!can_write}
/>
</Label>
</Section>
{#if loadingConfig}
<Section label="{serviceInfo?.serviceDisplayName} configuration">
<div class="flex items-center gap-2 text-secondary text-xs">

View File

@@ -199,7 +199,8 @@ export async function saveNativeTriggerFromCfg(
const requestBody: NativeTriggerData = {
script_path: triggerCfg.script_path,
is_flow: triggerCfg.is_flow,
service_config: triggerCfg.service_config
service_config: triggerCfg.service_config,
summary: triggerCfg.summary || undefined
}
const serviceName = NATIVE_TRIGGER_SERVICES[service].serviceDisplayName

View File

@@ -481,14 +481,15 @@ export function getLightConfig(
} else if (triggerType === 'email') {
return { local_part: trigger.local_part }
} else if (triggerType === 'nextcloud') {
return { event: trigger.service_config?.event ?? trigger.event }
return { event: trigger.service_config?.event ?? trigger.event, summary: trigger.summary }
} else if (triggerType === 'google') {
return {
trigger_type: trigger.service_config?.triggerType ?? trigger.trigger_type,
resource_id: trigger.service_config?.resourceId ?? trigger.resource_id,
resource_name: trigger.service_config?.resourceName ?? trigger.resource_name,
calendar_id: trigger.service_config?.calendarId ?? trigger.calendar_id,
calendar_name: trigger.service_config?.calendarName ?? trigger.calendar_name
calendar_name: trigger.service_config?.calendarName ?? trigger.calendar_name,
summary: trigger.summary
}
} else {
return undefined
@@ -524,8 +525,12 @@ export function getTriggerLabel(trigger: Trigger): string {
return `${config?.url}`
} else if (type === 'email' && config?.local_part) {
return `${config?.local_part}`
} else if (type === 'nextcloud' && config?.summary) {
return `${config.summary}`
} else if (type === 'nextcloud' && path) {
return `${path}`
} else if (type === 'google' && config?.summary) {
return `${config.summary}`
} else if (type === 'google' && path) {
const triggerType = config?.trigger_type ?? config?.triggerType
if (triggerType === 'calendar') {