mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
feat: History commit message can be updated from the UI (#2777)
* feat: History comit message can be updated from the UI * Script version history * Display commit message in left column * Fix FE compile break * sqlx prepare and fix openapi file
This commit is contained in:
committed by
GitHub
parent
20b0500a28
commit
874b7a50f7
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "362419eb262c83d6a98a0200b116e831ada60399fe5f55a56d930cc69aff2675"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM app WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "abb56dec024600d0d7a63212f63c3296196898003707f2cb3a890690173a51a5"
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg\n FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version\n WHERE a.workspace_id = $1 AND a.path = $2\n ORDER BY created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "app_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "version_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d02c1cfefc7d5f87ca4555a092f5fbc777a91271b80d0e488a8cae79afd56d8f"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3225,6 +3225,53 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewScriptWithDraft"
|
||||
|
||||
/w/{workspace}/scripts/history/p/{path}:
|
||||
get:
|
||||
summary: get history of a script by path
|
||||
operationId: getScriptHistoryByPath
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: script history
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ScriptHistory"
|
||||
|
||||
/w/{workspace}/scripts/history_update/h/{hash}/p/{path}:
|
||||
post:
|
||||
summary: update history of a script
|
||||
operationId: updateScriptHistory
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptHash"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
requestBody:
|
||||
description: Script deployment message
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deployment_msg:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: success
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/scripts/raw/p/{path}:
|
||||
get:
|
||||
summary: raw script by path
|
||||
@@ -4065,6 +4112,53 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppWithLastVersionWDraft"
|
||||
|
||||
/w/{workspace}/apps/history/p/{path}:
|
||||
get:
|
||||
summary: get app history by path
|
||||
operationId: getAppHistoryByPath
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: app history
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AppHistory"
|
||||
|
||||
/w/{workspace}/apps/history_update/a/{id}/v/{version}:
|
||||
post:
|
||||
summary: update app history
|
||||
operationId: updateAppHistory
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/PathId"
|
||||
- $ref: "#/components/parameters/PathVersion"
|
||||
requestBody:
|
||||
description: App deployment message
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deployment_msg:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: success
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps_u/public_app/{path}:
|
||||
get:
|
||||
summary: get public app by secret
|
||||
@@ -6695,6 +6789,12 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
PathVersion:
|
||||
name: version
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
Name:
|
||||
name: name
|
||||
in: path
|
||||
@@ -7133,6 +7233,16 @@ components:
|
||||
required:
|
||||
- hash
|
||||
|
||||
ScriptHistory:
|
||||
type: object
|
||||
properties:
|
||||
script_hash:
|
||||
type: string
|
||||
deployment_msg:
|
||||
type: string
|
||||
required:
|
||||
- script_hash
|
||||
|
||||
ScriptArgs:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
@@ -8572,6 +8682,16 @@ components:
|
||||
type: boolean
|
||||
draft: {}
|
||||
|
||||
AppHistory:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: integer
|
||||
deployment_msg:
|
||||
type: string
|
||||
required:
|
||||
- version
|
||||
|
||||
SlackToken:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -55,6 +55,8 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/update/*path", post(update_app))
|
||||
.route("/delete/*path", delete(delete_app))
|
||||
.route("/create", post(create_app))
|
||||
.route("/history/p/*path", get(get_app_history))
|
||||
.route("/history_update/a/:id/v/:version", post(update_app_history))
|
||||
}
|
||||
|
||||
pub fn unauthed_service() -> Router {
|
||||
@@ -125,6 +127,19 @@ pub struct AppWithLastVersionAndDraft {
|
||||
pub draft_only: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AppHistory {
|
||||
pub app_id: i64,
|
||||
pub version: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AppHistoryUpdate {
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
pub type StaticFields = HashMap<String, Box<RawValue>>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -313,6 +328,64 @@ async fn get_app_w_draft(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
async fn get_app_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<Vec<AppHistory>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let query_result = sqlx::query!(
|
||||
"SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg
|
||||
FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version
|
||||
WHERE a.workspace_id = $1 AND a.path = $2
|
||||
ORDER BY created_at DESC",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
).fetch_all(&mut *tx).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let result: Vec<AppHistory> = query_result
|
||||
.into_iter()
|
||||
.map(|row| AppHistory {
|
||||
app_id: row.app_id,
|
||||
version: row.version_id,
|
||||
deployment_msg: row.deployment_msg,
|
||||
})
|
||||
.collect();
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
async fn update_app_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, app_id, app_version)): Path<(String, i64, i64)>,
|
||||
Json(app_history_update): Json<AppHistoryUpdate>,
|
||||
) -> Result<()> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let app_path = sqlx::query_scalar!("SELECT path FROM app WHERE id = $1", app_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if app_path.is_none() {
|
||||
tx.commit().await?;
|
||||
return Err(Error::NotFound(
|
||||
format!("App with ID {app_id} not found").to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
w_id,
|
||||
app_path.unwrap(),
|
||||
app_version,
|
||||
app_history_update.deployment_msg,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn get_app_by_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -40,7 +40,7 @@ use windmill_common::{
|
||||
schedule::Schedule,
|
||||
scripts::{
|
||||
to_i64, HubScript, ListScriptQuery, ListableScript, NewScript, Schema, Script, ScriptHash,
|
||||
ScriptKind, ScriptLang,
|
||||
ScriptHistory, ScriptHistoryUpdate, ScriptKind, ScriptLang,
|
||||
},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{
|
||||
@@ -121,6 +121,11 @@ pub fn workspaced_service() -> Router {
|
||||
"/toggle_workspace_error_handler/p/*path",
|
||||
post(toggle_workspace_error_handler),
|
||||
)
|
||||
.route("/history/p/*path", get(get_script_history))
|
||||
.route(
|
||||
"/history_update/h/:hash/p/*path",
|
||||
post(update_script_history),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -724,6 +729,54 @@ async fn get_script_by_path_w_draft(
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn get_script_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<Vec<ScriptHistory>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let query_result = sqlx::query!(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by created_at DESC",
|
||||
w_id,
|
||||
path.to_path(),
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let result: Vec<ScriptHistory> = query_result
|
||||
.into_iter()
|
||||
.map(|row| ScriptHistory {
|
||||
script_hash: ScriptHash(row.hash),
|
||||
deployment_msg: row.deployment_msg,
|
||||
})
|
||||
.collect();
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
async fn update_script_history(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, script_hash, script_path)): Path<(String, ScriptHash, StripPath)>,
|
||||
Json(script_history_update): Json<ScriptHistoryUpdate>,
|
||||
) -> Result<()> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4",
|
||||
w_id,
|
||||
script_path.to_path(),
|
||||
script_hash.0,
|
||||
script_history_update.deployment_msg,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn list_paths(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -204,6 +204,18 @@ pub struct ListableScript {
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ScriptHistory {
|
||||
pub script_hash: ScriptHash,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ScriptHistoryUpdate {
|
||||
pub deployment_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx)]
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import PanelSection from './apps/editor/settingsPanel/common/PanelSection.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { ScriptService } from '$lib/gen'
|
||||
import { classNames, emptyString } from '$lib/utils'
|
||||
import { ScriptService, type ScriptHistory } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import FlowModuleScript from './flows/content/FlowModuleScript.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
import { ExternalLink, Pencil, ArrowRight, X } from 'lucide-svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let openDetails: boolean = false
|
||||
export let scriptPath: string
|
||||
|
||||
let selectedVersion: string | undefined = undefined
|
||||
let versions: string[] | undefined = undefined
|
||||
let deploymentMsgUpdateMode = false
|
||||
let deploymentMsgUpdate: string | undefined = undefined
|
||||
|
||||
let selectedVersion: ScriptHistory | undefined = undefined
|
||||
let versions: ScriptHistory[] | undefined = undefined
|
||||
let loading: boolean = false
|
||||
|
||||
async function loadVersions() {
|
||||
loading = true
|
||||
versions = (
|
||||
await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: scriptPath })
|
||||
).parent_hashes
|
||||
versions = await ScriptService.getScriptHistoryByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: scriptPath
|
||||
})
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function updateDeploymentMsg(scriptHash: string | undefined) {
|
||||
if (
|
||||
selectedVersion === undefined ||
|
||||
scriptHash === undefined ||
|
||||
emptyString(deploymentMsgUpdate)
|
||||
) {
|
||||
return
|
||||
}
|
||||
await ScriptService.updateScriptHistory({
|
||||
workspace: $workspaceStore!,
|
||||
path: scriptPath,
|
||||
hash: scriptHash,
|
||||
requestBody: {
|
||||
deployment_msg: deploymentMsgUpdate!
|
||||
}
|
||||
})
|
||||
selectedVersion.deployment_msg = deploymentMsgUpdate
|
||||
deploymentMsgUpdateMode = false
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
loadVersions()
|
||||
</script>
|
||||
|
||||
@@ -42,15 +67,23 @@
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedVersion == version ? 'bg-blue-100 text-blue-600' : ''
|
||||
selectedVersion?.script_hash == version.script_hash
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: ''
|
||||
)}
|
||||
on:click={() => (selectedVersion = version)}
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
deploymentMsgUpdate = undefined
|
||||
deploymentMsgUpdateMode = false
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">{version}</span>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.script_hash}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
{#if openDetails}
|
||||
<Button
|
||||
on:click={() => {
|
||||
dispatch('openDetails', { version })
|
||||
dispatch('openDetails', { version: version.script_hash })
|
||||
}}
|
||||
class="ml-2 inline-flex gap-1 text-xs items-center"
|
||||
size="xs"
|
||||
@@ -76,7 +109,67 @@
|
||||
<div class="h-full w-full overflow-auto">
|
||||
{#if selectedVersion}
|
||||
{#key selectedVersion}
|
||||
<FlowModuleScript path={scriptPath} hash={selectedVersion} />
|
||||
<div class="flex flex-col">
|
||||
<span class="flex flex-row text-sm p-2 text-tertiary">
|
||||
{#if deploymentMsgUpdateMode}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={deploymentMsgUpdate}
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation
|
||||
on:keypress|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter') updateDeploymentMsg(selectedVersion?.script_hash)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save Deployment Message"
|
||||
on:click={() => {
|
||||
updateDeploymentMsg(selectedVersion?.script_hash)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Abort"
|
||||
on:click={() => {
|
||||
deploymentMsgUpdateMode = false
|
||||
deploymentMsgUpdate = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if selectedVersion.deployment_msg}
|
||||
{selectedVersion.deployment_msg}
|
||||
{:else}
|
||||
Version {selectedVersion.script_hash}
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => {
|
||||
deploymentMsgUpdate = selectedVersion?.deployment_msg
|
||||
deploymentMsgUpdateMode = true
|
||||
}}
|
||||
title="Update commit message"
|
||||
class="flex items-center px-1 rounded-sm hover:text-primary text-secondary h-5"
|
||||
aria-label="Update commit message"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<FlowModuleScript path={scriptPath} hash={selectedVersion.script_hash} />
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
<div class="text-sm p-2 text-tertiary">Select a deployment version to see its details</div>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import DeploymentHistory from './DeploymentHistory.svelte'
|
||||
|
||||
let versions: number[] = []
|
||||
let appPath: string | undefined = undefined
|
||||
let historyBrowserDrawerOpen = false
|
||||
|
||||
export function open(newVersions: number[]) {
|
||||
versions = newVersions
|
||||
export function open(appPath: string) {
|
||||
appPath = appPath
|
||||
historyBrowserDrawerOpen = true
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
sendUserToast('App restored from previous deployment')
|
||||
updateApp(e.detail)
|
||||
}}
|
||||
{versions}
|
||||
{appPath}
|
||||
on:close={() => {
|
||||
historyBrowserDrawerOpen = false
|
||||
}}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
export let policy: Policy
|
||||
export let summary: string
|
||||
export let fromHub: boolean = false
|
||||
export let versions: number[]
|
||||
export let diffDrawer: DiffDrawer | undefined = undefined
|
||||
export let savedApp:
|
||||
| {
|
||||
@@ -493,7 +492,6 @@
|
||||
{#if $appStore}
|
||||
<AppEditorHeader
|
||||
on:restore
|
||||
{versions}
|
||||
{policy}
|
||||
{fromHub}
|
||||
bind:this={appEditorHeader}
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
|
||||
export let policy: Policy
|
||||
export let fromHub: boolean = false
|
||||
export let versions: number[]
|
||||
export let diffDrawer: DiffDrawer | undefined = undefined
|
||||
export let savedApp:
|
||||
| {
|
||||
@@ -836,7 +835,7 @@
|
||||
|
||||
<Drawer bind:open={historyBrowserDrawerOpen} size="1200px">
|
||||
<DrawerContent title="Deployment History" on:close={() => (historyBrowserDrawerOpen = false)}>
|
||||
<DeploymentHistory on:restore {versions} />
|
||||
<DeploymentHistory on:restore {appPath} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
|
||||
@@ -1,20 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
import { classNames, displayDate } from '$lib/utils'
|
||||
import { AppService, AppWithLastVersion } from '$lib/gen'
|
||||
import { classNames, displayDate, emptyString } from '$lib/utils'
|
||||
import { AppService, AppWithLastVersion, type AppHistory } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import AppPreview from './AppPreview.svelte'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Pencil, ArrowRight, X } from 'lucide-svelte'
|
||||
|
||||
export let versions: number[]
|
||||
export let appPath: string | undefined
|
||||
let loading: boolean = false
|
||||
|
||||
let selectedVersion: number | undefined = undefined
|
||||
let versions: AppHistory[] = []
|
||||
|
||||
let selectedVersion: AppHistory | undefined = undefined
|
||||
let selected: AppWithLastVersion | undefined = undefined
|
||||
|
||||
$: selectedVersion !== undefined && loadValue(selectedVersion)
|
||||
let deploymentMsgUpdateMode = false
|
||||
let deploymentMsgUpdate: string | undefined = undefined
|
||||
|
||||
$: selectedVersion !== undefined && loadValue(selectedVersion.version)
|
||||
|
||||
async function loadVersions() {
|
||||
console.log('loading versions')
|
||||
if (appPath === undefined) {
|
||||
return
|
||||
}
|
||||
console.log('loading versions')
|
||||
|
||||
loading = true
|
||||
versions = await AppService.getAppHistoryByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: appPath
|
||||
})
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function loadValue(version: number) {
|
||||
let app = await AppService.getAppByVersion({ workspace: $workspaceStore!, id: version })
|
||||
@@ -22,32 +44,63 @@
|
||||
selected = app
|
||||
}
|
||||
|
||||
$: reversedVersions = versions.slice().reverse()
|
||||
async function updateDeploymentMsg(appId: number | undefined, appVersion: number | undefined) {
|
||||
if (
|
||||
selectedVersion === undefined ||
|
||||
appId === undefined ||
|
||||
appVersion === undefined ||
|
||||
emptyString(deploymentMsgUpdate)
|
||||
) {
|
||||
return
|
||||
}
|
||||
await AppService.updateAppHistory({
|
||||
workspace: $workspaceStore!,
|
||||
id: appId,
|
||||
version: appVersion,
|
||||
requestBody: {
|
||||
deployment_msg: deploymentMsgUpdate!
|
||||
}
|
||||
})
|
||||
selectedVersion.deployment_msg = deploymentMsgUpdate
|
||||
deploymentMsgUpdateMode = false
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
loadVersions()
|
||||
</script>
|
||||
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={20}>
|
||||
<PanelSection title="Past Deployments">
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each reversedVersions ?? [] as version}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedVersion == version ? 'bg-blue-100 text-blue-600' : ''
|
||||
)}
|
||||
on:click={() => (selectedVersion = version)}
|
||||
>
|
||||
<span class="text-xs truncate">{version}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !loading}
|
||||
{#if versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each versions ?? [] as version}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedVersion?.version == version.version ? 'bg-blue-100 text-blue-600' : ''
|
||||
)}
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
deploymentMsgUpdateMode = false
|
||||
deploymentMsgUpdate = undefined
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.version}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
@@ -56,14 +109,74 @@
|
||||
<div class="h-full w-full overflow-auto">
|
||||
{#if selectedVersion}
|
||||
{#if selected}
|
||||
<div class="flex justify-between">
|
||||
<h3 class="p-1">Deployed {displayDate(selected.created_at)} by {selected.created_by}</h3
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<Button on:click={() => window.open(`/apps/add?template_id=${selectedVersion}`)}>
|
||||
<div class="flex flex-col justify-between">
|
||||
<span class="flex flex-row text-sm p-1 text-tertiary">
|
||||
{#if deploymentMsgUpdateMode}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={deploymentMsgUpdate}
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation
|
||||
on:keypress|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter')
|
||||
updateDeploymentMsg(selected?.id, selectedVersion?.version)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save Deployment Message"
|
||||
on:click={() => {
|
||||
updateDeploymentMsg(selected?.id, selectedVersion?.version)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Abort"
|
||||
on:click={() => {
|
||||
deploymentMsgUpdateMode = false
|
||||
deploymentMsgUpdate = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if selectedVersion.deployment_msg}
|
||||
{selectedVersion.deployment_msg}
|
||||
{:else}
|
||||
Deployed {displayDate(selected.created_at)} by {selected.created_by}
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => {
|
||||
deploymentMsgUpdate = selectedVersion?.deployment_msg
|
||||
deploymentMsgUpdateMode = true
|
||||
}}
|
||||
title="Update commit message"
|
||||
class="flex items-center px-1 rounded-sm hover:text-primary text-secondary h-5"
|
||||
aria-label="Update commit message"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex p-1 gap-2">
|
||||
<Button
|
||||
size="xs"
|
||||
on:click={() => window.open(`/apps/add?template_id=${selectedVersion}`)}
|
||||
>
|
||||
Restore as fork
|
||||
</Button>
|
||||
<Button on:click={() => dispatch('restore', selected)}
|
||||
<Button size="xs" on:click={() => dispatch('restore', selected)}
|
||||
>Redeploy with that version
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
path
|
||||
})) as unknown as AppWithLastVersion
|
||||
|
||||
appDeploymentHistory.open(app.versions)
|
||||
appDeploymentHistory.open(app.path)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
{#if value}
|
||||
<div class="h-screen">
|
||||
{#key value}
|
||||
<AppEditor versions={[]} {summary} app={value} path={''} {policy} fromHub={hubId != null} />
|
||||
<AppEditor {summary} app={value} path={''} {policy} fromHub={hubId != null} />
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
app = e.detail
|
||||
redraw++
|
||||
}}
|
||||
versions={app.versions}
|
||||
summary={app.summary}
|
||||
app={app.value}
|
||||
path={app.path}
|
||||
|
||||
Reference in New Issue
Block a user