fix: improve performance of list users

This commit is contained in:
Ruben Fiszel
2024-02-25 11:41:41 +01:00
parent 36ef54189f
commit 88799b935e
3 changed files with 92 additions and 38 deletions
+25 -8
View File
@@ -1734,6 +1734,31 @@ paths:
items:
$ref: "#/components/schemas/User"
/w/{workspace}/users/list_usage:
get:
summary: list users usage
operationId: listUsersUsage
tags:
- user
parameters:
- $ref: "#/components/parameters/WorkspaceId"
responses:
"200":
description: user
content:
application/json:
schema:
type: array
items:
type: object
properties:
email:
type: string
executions:
type: number
required:
email
/w/{workspace}/users/list_usernames:
get:
summary: list usernames
@@ -8399,8 +8424,6 @@ components:
type: array
items:
type: string
usage:
$ref: "#/components/schemas/Usage"
required:
- email
- username
@@ -8412,12 +8435,6 @@ components:
- folders
- folders_owners
Usage:
type: object
properties:
executions:
type: number
Login:
type: object
properties:
+38 -24
View File
@@ -57,6 +57,7 @@ const COOKIE_PATH: &str = "/";
pub fn workspaced_service() -> Router {
Router::new()
.route("/list", get(list_users))
.route("/list_usage", get(list_user_usage))
.route("/list_usernames", get(list_usernames))
.route("/exists", post(exists_username))
.route("/update/:user", post(update_workspace_user))
@@ -542,16 +543,11 @@ pub struct User {
pub role: Option<String>,
}
#[derive(FromRow, Serialize)]
pub struct Usage {
pub executions: i64,
}
#[derive(Serialize)]
pub struct UserWithUsage {
#[serde(flatten)]
pub user: User,
pub usage: Usage,
pub email: String,
pub executions: Option<i64>,
}
#[derive(FromRow, Serialize, Debug)]
@@ -717,37 +713,55 @@ async fn list_users(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
) -> JsonResult<Vec<UserWithUsage>> {
) -> JsonResult<Vec<User>> {
if *CLOUD_HOSTED && w_id == "demo" {
require_admin(authed.is_admin, &authed.username)?;
}
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query(
let rows = sqlx::query_as!(User,
"
SELECT usr.*, usage.*
SELECT *
FROM usr
, LATERAL (
SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions
FROM completed_job
WHERE workspace_id = $1
AND job_kind NOT IN ('flow', 'flowpreview')
AND email = usr.email
AND now() - '1 week'::interval < created_at
) usage
WHERE workspace_id = $1
",
", w_id
)
.bind(&w_id)
.try_map(|row| {
// flatten not released yet https://github.com/launchbadge/sqlx/pull/1959
Ok(UserWithUsage { user: FromRow::from_row(&row)?, usage: FromRow::from_row(&row)? })
})
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(rows))
}
async fn list_user_usage(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>) -> JsonResult<Vec<UserWithUsage>> {
if *CLOUD_HOSTED && w_id == "demo" {
require_admin(authed.is_admin, &authed.username)?;
}
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as!(
UserWithUsage,
"
SELECT usr.email, usage.executions
FROM usr
, LATERAL (
SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions
FROM completed_job
WHERE workspace_id = $1
AND job_kind NOT IN ('flow', 'flowpreview')
AND email = usr.email
AND now() - '1 week'::interval < created_at
) usage
WHERE workspace_id = $1
",
w_id
)
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(rows))
}
async fn list_users_as_super_admin(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -11,16 +11,18 @@
import Head from '$lib/components/table/Head.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import type { User } from '$lib/gen'
import type { CancelablePromise, User } from '$lib/gen'
import { UserService, WorkspaceService, type WorkspaceInvite } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { Mails, Search } from 'lucide-svelte'
import { Loader2, Mails, Search } from 'lucide-svelte'
import SearchItems from '../SearchItems.svelte'
import Cell from '../table/Cell.svelte'
import Row from '../table/Row.svelte'
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
import { isCloudHosted } from '$lib/cloud'
import { truncate } from '$lib/utils'
import { onDestroy } from 'svelte'
let users: User[] | undefined = undefined
let invites: WorkspaceInvite[] = []
@@ -38,6 +40,22 @@
autoAdd = settings.auto_add
}
let getUsagePromise: CancelablePromise<Array<{
email: string;
executions?: number;
}>> | undefined = undefined
let usage: Record<string, number> | undefined = undefined
async function getUsage() {
getUsagePromise = UserService.listUsersUsage({ workspace: $workspaceStore! })
const res = await getUsagePromise
usage = res.reduce((acc, { email, executions }) => {
acc[email] = executions ?? 0
return acc
}, {} as Record<string, number>)
}
async function listUsers(): Promise<void> {
users = await UserService.listUsers({ workspace: $workspaceStore! })
}
@@ -58,11 +76,16 @@
if ($workspaceStore) {
getDisallowedAutoDomain()
listUsers()
getUsage()
listInvites()
loadSettings()
}
}
onDestroy(() => {
getUsagePromise?.cancel()
})
let deleteConfirmedCallback: (() => void) | undefined = undefined
async function removeAllInvitesFromDomain() {
@@ -145,11 +168,11 @@
</Head>
<tbody class="divide-y bg-surface">
{#if filteredUsers}
{#each filteredUsers.slice(0, nbDisplayed) as { email, username, is_admin, operator, usage, disabled } (email)}
{#each filteredUsers.slice(0, nbDisplayed) as { email, username, is_admin, operator, disabled } (email)}
<tr class="!hover:bg-surface-hover">
<Cell first>{email}</Cell>
<Cell>{username}</Cell>
<Cell>{usage?.executions}</Cell>
<Cell first>{truncate(email, 20)}</Cell>
<Cell>{truncate(username, 30)}</Cell>
<Cell>{#if usage?.[email] != undefined}{usage?.[email]}{:else}<Loader2 size={14} class="animate-spin" />{/if}</Cell>
<Cell>
<div class="flex gap-1">
{#if disabled}