Get variables usages

This commit is contained in:
Diego Imbert
2025-07-09 17:06:50 +02:00
parent aa5187ad4b
commit b11ced4e29
8 changed files with 64 additions and 22 deletions
+8
View File
@@ -3070,6 +3070,10 @@ paths:
in: query
schema:
type: string
- name: get_usages
in: query
schema:
type: boolean
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
responses:
@@ -14261,6 +14265,10 @@ components:
expires_at:
type: string
format: date-time
usages:
type: array
items:
$ref: "#/components/schemas/AssetUsage"
required:
- workspace_id
- path
+1 -1
View File
@@ -122,7 +122,7 @@ pub struct ListableResource {
pub is_expired: Option<bool>,
pub refresh_error: Option<String>,
pub account: Option<i32>,
pub usages: Option<Vec<Value>>, // AssetUsage[] | null
pub usages: Option<Vec<serde_json::Value>>, // AssetUsage[] | null
}
#[derive(Deserialize)]
+16 -3
View File
@@ -7,7 +7,9 @@
*/
use crate::{
db::{ApiAuthed, DB}, users::{maybe_refresh_folders, require_owner_of_path}, webhook_util::{WebhookMessage, WebhookShared}
db::{ApiAuthed, DB},
users::{maybe_refresh_folders, require_owner_of_path},
webhook_util::{WebhookMessage, WebhookShared},
};
use axum::{
@@ -21,7 +23,10 @@ use serde_json::Value;
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
use windmill_audit::ActionKind;
use windmill_common::{
db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, variables::{
db::UserDB,
error::{Error, JsonResult, Result},
utils::{not_found_if_none, paginate, Pagination, StripPath},
variables::{
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
},
worker::CLOUD_HOSTED,
@@ -80,6 +85,7 @@ async fn list_contextual_variables(
#[derive(Deserialize)]
struct ListVariableQuery {
path_start: Option<String>,
get_usages: Option<bool>,
}
async fn list_variables(
@@ -99,7 +105,13 @@ async fn list_variables(
account.refresh_error,
resource.path IS NOT NULL as is_linked,
account.refresh_token != '' as is_refreshed,
variable.expires_at
variable.expires_at,
CASE WHEN $6 IS TRUE THEN
(SELECT ARRAY_AGG(jsonb_build_object(
'access_type', asset.usage_access_type, 'path', asset.usage_path, 'kind', asset.usage_kind))
FROM asset
WHERE asset.path = variable.path AND asset.workspace_id = $1 AND asset.kind = 'variable')
ELSE NULL END as usages
from variable
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
@@ -114,6 +126,7 @@ async fn list_variables(
.bind(&lq.path_start.unwrap_or_default())
.bind(per_page as i32)
.bind(offset as i32)
.bind(lq.get_usages.unwrap_or(false))
.fetch_all(&mut *tx)
.await?;
+1 -1
View File
@@ -39,7 +39,7 @@ pub struct Asset {
pub struct AssetUsage {
pub path: String,
pub kind: AssetUsageKind,
pub access_type: AssetUsageAccessType,
pub access_type: Option<AssetUsageAccessType>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
+1
View File
@@ -43,6 +43,7 @@ pub struct ListableVariable {
pub refresh_error: Option<String>,
pub is_linked: Option<bool>,
pub expires_at: Option<chrono::DateTime<Utc>>,
pub usages: Option<Vec<serde_json::Value>>, // AssetUsage[] | null
}
#[derive(Serialize, Deserialize, sqlx::FromRow)]
@@ -811,11 +811,11 @@
</Cell>
<Cell>
<div class="flex w-full flex-row gap-3 items-center text-center">
<div class="flex w-full flex-row gap-3 items-center">
<a
href={undefined}
class={twMerge(
'min-w-20 select-none',
'min-w-16',
usages?.length ? 'cursor-pointer' : 'text-tertiary/80'
)}
onclick={() => {
@@ -17,11 +17,16 @@
import TableSimple from '$lib/components/TableSimple.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import VariableEditor from '$lib/components/VariableEditor.svelte'
import type { ContextualVariable, ListableVariable, WorkspaceDeployUISettings } from '$lib/gen'
import type {
AssetUsage,
ContextualVariable,
ListableVariable,
WorkspaceDeployUISettings
} from '$lib/gen'
import { OauthService, VariableService, WorkspaceService } from '$lib/gen'
import { enterpriseLicense, userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { canWrite, isOwner, truncate } from '$lib/utils'
import { canWrite, isOwner, pluralize, truncate } from '$lib/utils'
import { isDeployable, ALL_DEPLOYABLE } from '$lib/utils_deployable'
import {
Plus,
@@ -37,6 +42,13 @@
Circle
} from 'lucide-svelte'
import { untrack } from 'svelte'
import { twMerge } from 'tailwind-merge'
type Props = {
onOpenUsages: (usages: AssetUsage[]) => void
}
let { onOpenUsages }: Props = $props()
type ListableVariableW = ListableVariable & { canWrite: boolean }
@@ -76,7 +88,9 @@
// If relative, the dropdown is positioned relative to its button
async function loadVariables(): Promise<void> {
variables = (await VariableService.listVariable({ workspace: $workspaceStore! })).map((x) => {
variables = (
await VariableService.listVariable({ workspace: $workspaceStore!, getUsages: true })
).map((x) => {
return {
canWrite: canWrite(x.path, x.extra_perms!, $userStore) && x.workspace_id == $workspaceStore,
...x
@@ -226,7 +240,7 @@
</tr>
</Head>
<tbody class="divide-y">
{#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked }}
{#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked, usages }}
<Row>
<Cell class="!px-0 text-center w-12" first>
<SharedBadge {canWrite} extraPerms={extra_perms} />
@@ -268,9 +282,20 @@
<span class="text-xs text-tertiary">{truncate(description ?? '', 50)} </span>
</Cell>
<Cell class="text-center">
<Cell class="">
<div class="flex flex-row items-center gap-4">
<a href={undefined} class="cursor-pointer min-w-20"> 0 usages </a>
<a
href={undefined}
class={twMerge(
'min-w-16',
usages?.length ? 'cursor-pointer' : 'text-tertiary/80'
)}
onclick={() => {
if (usages?.length) onOpenUsages(usages)
}}
>
{pluralize(usages?.length ?? 0, 'usage')}
</a>
{#if is_linked}
<Popover notClickable>
<Link size={16} />
@@ -4,9 +4,7 @@
import { DrawerContent, Tab, Tabs } from '$lib/components/common'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import DbManagerDrawer from '$lib/components/DBManagerDrawer.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
import { type AssetUsageAccessType, type AssetUsageKind } from '$lib/gen'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import {
@@ -29,9 +27,6 @@
}[]
}
| undefined = $state()
let s3FilePicker: S3FilePicker | undefined = $state()
let dbManagerDrawer: DbManagerDrawer | undefined = $state()
let selectedTab: 'resources' | 'variables' | 's3objects' = $state('resources')
let resourceListPage: ResourceListPage | undefined = $state()
@@ -103,7 +98,10 @@
onOpenUsages={(usages) => (usagesDrawerData = { usages })}
/>
{:else if selectedTab === 'variables'}
<VariablesListPage bind:this={variablesListPage} />
<VariablesListPage
bind:this={variablesListPage}
onOpenUsages={(usages) => (usagesDrawerData = { usages })}
/>
{:else if selectedTab === 's3objects'}
<S3ObjectsListPage />
{/if}
@@ -143,6 +141,3 @@
</ul>
</DrawerContent>
</Drawer>
<S3FilePicker bind:this={s3FilePicker} readOnlyMode />
<DbManagerDrawer bind:this={dbManagerDrawer} />