nit UI + more tests
This commit is contained in:
411
backend/tests/error_handler.rs
Normal file
411
backend/tests/error_handler.rs
Normal file
@@ -0,0 +1,411 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
/// Test that workspace error handler can be set and removed via database operations
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_settings(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let _server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Initially error_handler should be NULL
|
||||
let initial = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(initial.is_none());
|
||||
|
||||
// Set error handler with all options
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler", "extra_args": {"notify": true}, "muted_on_cancel": true, "muted_on_user_path": false}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let after_set = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
after_set,
|
||||
Some("script/f/test/error_handler".to_string())
|
||||
);
|
||||
|
||||
// Verify extra_args
|
||||
let extra_args = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(extra_args.is_some());
|
||||
|
||||
// Verify muted_on_cancel
|
||||
let muted_on_cancel = sqlx::query_scalar!(
|
||||
r#"SELECT (error_handler->>'muted_on_cancel')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(muted_on_cancel, Some(true));
|
||||
|
||||
// Verify muted_on_user_path
|
||||
let muted_on_user_path = sqlx::query_scalar!(
|
||||
r#"SELECT (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(muted_on_user_path, Some(false));
|
||||
|
||||
// Remove error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = NULL
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let after_remove = sqlx::query_scalar!(
|
||||
r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(after_remove.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that a failed job triggers the workspace error handler
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_triggered_on_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
let error_handler_code = r#"
|
||||
export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, error: any) {
|
||||
console.log("Error handler called for job:", job_id);
|
||||
return { handled: true, original_path: path };
|
||||
}
|
||||
"#;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 1111111111, 'f/test/error_handler', $1, 'deno', 'script', 'test-user', '{}', 'Error handler script', 'Handles failed job completions', '')
|
||||
"#,
|
||||
error_handler_code
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a script that will fail
|
||||
let failing_script_code = "export function main() { throw new Error('intentional failure'); }";
|
||||
let failing_script_hash: i64 = 2222222222;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', $1, 'f/test/failing_script', $2, 'deno', 'script', 'test-user', '{}', 'Failing test script', 'A script that always fails', '')
|
||||
"#,
|
||||
failing_script_hash,
|
||||
failing_script_code
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler in workspace_settings
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create the error_handler group
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'The group the error handler acts on behalf of', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the failing script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(failing_script_hash),
|
||||
path: "f/test/failing_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
// Verify the job actually failed
|
||||
assert!(!completed_job.success, "Job should have failed");
|
||||
|
||||
let main_job_id = completed_job.id;
|
||||
|
||||
// Wait for the error handler job to be created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
// Verify the error handler job was created
|
||||
let error_handler_job = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
runnable_path,
|
||||
permissioned_as_email,
|
||||
parent_job
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_some(),
|
||||
"Error handler job should have been created"
|
||||
);
|
||||
|
||||
let handler_job = error_handler_job.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
handler_job.runnable_path.as_deref(),
|
||||
Some("f/test/error_handler"),
|
||||
"Error handler should run the configured script"
|
||||
);
|
||||
assert_eq!(
|
||||
handler_job.permissioned_as_email.as_str(),
|
||||
"error_handler@windmill.dev",
|
||||
"Error handler should run as error_handler user"
|
||||
);
|
||||
assert_eq!(
|
||||
handler_job.parent_job,
|
||||
Some(main_job_id),
|
||||
"Error handler should have the failed job as parent"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that error handler is NOT triggered when ws_error_handler_muted is set on the script
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_muted_on_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 3333333333, 'f/test/error_handler', 'export function main() { return "handled"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a failing script with ws_error_handler_muted = true
|
||||
let failing_script_hash: i64 = 4444444444;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted)
|
||||
VALUES ('test-workspace', $1, 'f/test/muted_failing_script', 'export function main() { throw new Error("fail"); }', 'deno', 'script', 'test-user', '{}', '', '', '', true)
|
||||
"#,
|
||||
failing_script_hash,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the muted failing script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(failing_script_hash),
|
||||
path: "f/test/muted_failing_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
assert!(!completed_job.success, "Job should have failed");
|
||||
|
||||
// Wait and check that NO error handler job was created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
let error_handler_job = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_none(),
|
||||
"Error handler should NOT have been triggered for a muted script"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that error handler is NOT triggered on successful job completion
|
||||
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_error_handler_not_triggered_on_success(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
// Create the error handler script
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', 5555555555, 'f/test/error_handler', 'export function main() { return "handled"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Create a successful script
|
||||
let success_script_hash: i64 = 6666666666;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
||||
VALUES ('test-workspace', $1, 'f/test/success_script', 'export function main() { return "ok"; }', 'deno', 'script', 'test-user', '{}', '', '', '')
|
||||
"#,
|
||||
success_script_hash,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Set up the error handler
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspace_settings
|
||||
SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}')
|
||||
ON CONFLICT DO NOTHING
|
||||
"#
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Run the successful script
|
||||
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
||||
hash: ScriptHash(success_script_hash),
|
||||
path: "f/test/success_script".to_string(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
language: ScriptLang::Deno,
|
||||
priority: None,
|
||||
apply_preprocessor: false,
|
||||
concurrency_settings: ConcurrencySettings::default(),
|
||||
debouncing_settings: DebouncingSettings::default(),
|
||||
})
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await;
|
||||
|
||||
assert!(completed_job.success, "Job should have succeeded");
|
||||
|
||||
// Wait and check that NO error handler job was created
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
let error_handler_job = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM v2_job
|
||||
WHERE workspace_id = 'test-workspace'
|
||||
AND permissioned_as_email = 'error_handler@windmill.dev'
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
error_handler_job.is_none(),
|
||||
"Error handler should NOT have been triggered for a successful job"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
{/snippet}
|
||||
<TextInput
|
||||
inputProps={{ type: 'number' }}
|
||||
bind:value={() => min?.toString(), (v) => (min = v ? parseInt(v) : undefined)}
|
||||
bind:value={() => min?.toString(), (v) => (min = v !== '' && v != null ? parseInt(v) : undefined)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
{/snippet}
|
||||
<TextInput
|
||||
inputProps={{ type: 'number' }}
|
||||
bind:value={() => max?.toString(), (v) => (max = v ? parseInt(v) : undefined)}
|
||||
bind:value={() => max?.toString(), (v) => (max = v !== '' && v != null ? parseInt(v) : undefined)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
>
|
||||
<div class="leading-6 font-semibold text-sm w-full flex justify-between">
|
||||
<div>Migrate to CSS editor</div><CloseButton
|
||||
on:close={() => (migrationModalOpen = false)}
|
||||
onClick={() => (migrationModalOpen = false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
<CloseButton
|
||||
noBg
|
||||
small
|
||||
on:close={() => {
|
||||
onClick={() => {
|
||||
items = items.filter((_, i) => i !== index)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
bind:value={items[index].value}
|
||||
/>
|
||||
<div class="absolute right-8">
|
||||
<CloseButton noBg small on:close={() => deleteSubgrid(index)} />
|
||||
<CloseButton noBg small onClick={() => deleteSubgrid(index)} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center gap-2">
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
/>
|
||||
{#if deletable}
|
||||
<div class="flex flex-row-reverse -mt-4">
|
||||
<CloseButton noBg on:close={() => dispatch('delete', k)} />
|
||||
<CloseButton noBg onClick={() => dispatch('delete', k)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<CloseButton
|
||||
small
|
||||
on:close={() => deleteComponent(component.id, item.originalIndex)}
|
||||
onClick={() => deleteComponent(component.id, item.originalIndex)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div
|
||||
class="flex justify-between {right ? '' : 'flex-row-reverse'} items-center gap-1 px-3 py-2"
|
||||
>
|
||||
<CloseButton on:close={() => secondaryMenu?.close()} />
|
||||
<CloseButton onClick={() => secondaryMenu?.close()} />
|
||||
{#if $secondaryMenu?.props?.type === 'style'}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<div class="text-xs font-bold"> Style Panel</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Button from './button/Button.svelte'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -10,17 +9,15 @@
|
||||
Icon?: any | undefined
|
||||
class?: string
|
||||
id?: string | undefined
|
||||
onClick?: () => void | undefined | any
|
||||
onClick?: (e: Event) => void
|
||||
}
|
||||
|
||||
let { noBg = false, small = false, Icon, class: className, id, onClick }: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Button
|
||||
on:click={() => (dispatch('close'), onClick?.())}
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
{onClick}
|
||||
onPointerdown={(e) => e.stopPropagation()}
|
||||
{id}
|
||||
startIcon={{ icon: Icon ?? X }}
|
||||
iconOnly
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
dropdownBtnClasses?: string
|
||||
dropdownItems?: MenuItem[] | (() => MenuItem[]) | undefined
|
||||
hideDropdown?: boolean
|
||||
onClick?: (e?: Event) => void
|
||||
onClick?: (e: Event) => void
|
||||
children?: import('svelte').Snippet
|
||||
tooltip?: import('svelte').Snippet
|
||||
[key: string]: any
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloseButton on:close Icon={CloseIcon} id="{id}-close-btn" />
|
||||
<CloseButton onClick={() => dispatch('close')} Icon={CloseIcon} id="{id}-close-btn" />
|
||||
</div>
|
||||
<span class="font-semibold text-emphasis truncate text-lg max-w-sm"
|
||||
>{title ?? ''}
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
{style}
|
||||
>
|
||||
{#if kind == 'X'}
|
||||
<div class="absolute top-4 right-4"><CloseButton on:close={() => (open = false)} /></div
|
||||
<div class="absolute top-4 right-4"><CloseButton onClick={() => (open = false)} /></div
|
||||
>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
onblur={handleBlur}
|
||||
/>
|
||||
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
|
||||
><span class="invisible">days</span><span>{day && day > 1 ? 'days' : 'day'}</span></span
|
||||
><span class="invisible">days</span><span>{day !== 1 ? 'days' : 'day'}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
@@ -166,7 +166,7 @@
|
||||
onblur={handleBlur}
|
||||
/>
|
||||
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
|
||||
><span class="invisible">hrs</span><span>{hour && hour > 1 ? 'hrs' : 'hr'}</span></span
|
||||
><span class="invisible">hrs</span><span>{hour !== 1 ? 'hrs' : 'hr'}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
@@ -190,7 +190,7 @@
|
||||
onblur={handleBlur}
|
||||
/>
|
||||
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
|
||||
><span class="invisible">mins</span><span>{min && min > 1 ? 'mins' : 'min'}</span></span
|
||||
><span class="invisible">mins</span><span>{min !== 1 ? 'mins' : 'min'}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
@@ -214,7 +214,7 @@
|
||||
onblur={handleBlur}
|
||||
/>
|
||||
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
|
||||
><span class="invisible">secs</span><span>{sec && sec > 1 ? 'secs' : 'sec'}</span></span
|
||||
><span class="invisible">secs</span><span>{sec !== 1 ? 'secs' : 'sec'}</span></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,7 +223,7 @@
|
||||
class="bg-transparent text-secondary hover:text-primary"
|
||||
noBg
|
||||
small
|
||||
on:close={() => {
|
||||
onClick={() => {
|
||||
seconds = defaultValue
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<CloseButton
|
||||
class="text-hint bg-transparent border-none"
|
||||
small
|
||||
on:close={(e) => (onRemove(item), e.stopPropagation())}
|
||||
onClick={(e) => { e.stopPropagation(); onRemove(item) }}
|
||||
/>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
noBg
|
||||
class="ml-2 remove-all bg-transparent text-hint"
|
||||
small
|
||||
on:close={(e) => (clearValue(), e.stopPropagation())}
|
||||
onClick={(e) => { e.stopPropagation(); clearValue() }}
|
||||
/>
|
||||
{/if}
|
||||
<SelectDropdown
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
class="bg-transparent text-secondary hover:text-primary"
|
||||
noBg
|
||||
small
|
||||
on:close={clearValue}
|
||||
onClick={clearValue}
|
||||
/>
|
||||
</div>
|
||||
{:else if RightIcon}
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell class="w-12">
|
||||
<CloseButton small on:close={() => removeDataTable(dataTableIndex)} />
|
||||
<CloseButton small onClick={() => removeDataTable(dataTableIndex)} />
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
|
||||
@@ -378,7 +378,7 @@
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell class="w-12">
|
||||
<CloseButton small on:close={() => removeDucklake(ducklakeIndex)} />
|
||||
<CloseButton small onClick={() => removeDucklake(ducklakeIndex)} />
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
{#if tableRow[0] !== null}
|
||||
<CloseButton
|
||||
small
|
||||
on:close={() => {
|
||||
onClick={() => {
|
||||
if (s3ResourceSettings.secondaryStorage) {
|
||||
s3ResourceSettings.secondaryStorage.splice(idx - 1, 1)
|
||||
s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage]
|
||||
|
||||
Reference in New Issue
Block a user