mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
Get variables usages
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user