Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/si/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"workspace": [],
"tasks": {
"dev": "deno run --allow-net --allow-env --allow-read --env-file=.env.local --allow-write --watch main.ts",
"dev": "deno run --unstable-sloppy-imports --allow-net --allow-env --allow-read --env-file=.env.local --allow-write --watch main.ts",
"build": "deno compile --allow-net --allow-env --allow-read --allow-write main.ts",
"lint": "deno lint",
"test": "deno test --allow-env --allow-read --allow-write"
Expand Down
27 changes: 27 additions & 0 deletions bin/si/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions bin/si/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { type ComponentGetOptions } from "./component/get.ts";
import { type ComponentUpdateOptions } from "./component/update.ts";
import { type ComponentDeleteOptions } from "./component/delete.ts";
import { type ComponentSearchOptions } from "./component/search.ts";
import { callTui } from "./command/tui.ts";

/** Current version of the SI CLI */
const VERSION = "0.1.0";
Expand Down Expand Up @@ -175,7 +176,9 @@ function buildCommand() {
// deno-lint-ignore no-explicit-any
.command("template", buildTemplateCommand() as any)
// deno-lint-ignore no-explicit-any
.command("whoami", buildWhoamiCommand() as any);
.command("whoami", buildWhoamiCommand() as any)
// deno-lint-ignore no-explicit-any
.command("tui", buildTuiCommand() as any);
}

/**
Expand Down Expand Up @@ -267,7 +270,7 @@ function buildRemoteSchemaCommand() {
"--builtins",
"Include builtin schemas (schemas you don't own). By default, builtins are skipped.",
)
.action(async ({ root, apiBaseUrl, apiToken, builtins }, ...schemaNames) => {
.action(async ({ root, apiBaseUrl, apiToken }, ...schemaNames) => {
const project = createProject(root);
const apiCtx = await createApiContext(apiBaseUrl, apiToken);
let finalSchemaNames;
Expand All @@ -282,7 +285,6 @@ function buildRemoteSchemaCommand() {
project,
apiCtx,
finalSchemaNames,
builtins ?? false,
);
}),
)
Expand Down Expand Up @@ -355,6 +357,22 @@ function buildWhoamiCommand() {
});
}

/**
* Builds the tui command.
*
* @returns A SubCommand to start the TUI
* @internal
*/
function buildTuiCommand() {
return createSubCommand()
.description("Starts the TUI")
.action(async ({ apiBaseUrl, apiToken }) => {
const apiCtx = await createApiContext(apiBaseUrl, apiToken);

await callTui(Context.instance(), apiCtx);
});
}

/**
* Builds the project init subcommands.
*
Expand Down
118 changes: 118 additions & 0 deletions bin/si/src/command/tui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Context } from "../context.ts";
import { ApiContext } from "../api.ts";
import * as p from 'npm:@clack/prompts';
import color from 'npm:picocolors';
import { apiConfig } from "../si_client.ts";
// import { ChangeSetsApi, ChangeSetViewV1, MvApi } from "https://jsr.io/@systeminit/api-client/1.9.0/api.ts";
import { ChangeSetsApi, ChangeSetViewV1, MvApi } from "../../../../generated-sdks/typescript/api.ts";

const EXIT = "-1";

export async function callTui(ctx: Context, _apiCtx: ApiContext) {
const changeSetsApi = new ChangeSetsApi(apiConfig);
const mvSetsApi = new MvApi(apiConfig);
const workspaceId = ctx.workspaceId;
if (!workspaceId) throw new Error("No Workspace");

p.updateSettings({
aliases: {
w: 'up',
s: 'down',
a: 'left',
d: 'right',
},
});

p.intro("Welcome! Let's review propsed changes in your workspace")

const cs = p.spinner({
onCancel: () => {
process.exit(0);
}
});
cs.start(`${color.bgBlack(color.greenBright("Retrieving change sets"))}`);
const response = await changeSetsApi.listChangeSets({ workspaceId });
const changeSets = response.data.changeSets as ChangeSetViewV1[];
cs.stop();

const options = changeSets.filter((c) => !c.isHead).map((c) => {
return {
value: c.id,
label: c.name,
}
})
const changeSetId = await p.select({
message: "Choose a change set:",
options,
});

if (p.isCancel(changeSetId)) {
p.cancel("Cancelled, exiting...")
process.exit(0);
}

const c = p.spinner();

c.start(`${color.bgBlack(color.greenBright("Retrieving components"))}`);
const componentList = await mvSetsApi.get({
workspaceId,
changeSetId,
entityId: workspaceId,
kind: "ComponentList"
})

// N+1 requests are horribly in-efficient
// if we wanted to invest in a TUI we could do the same
// "sync all the MVs" on start, open a web socket, etc
const componentDetails = await Promise.all(componentList.data.data.components.map((c) => {
return mvSetsApi.get({
workspaceId,
changeSetId,
entityId: c.id,
kind: "ComponentInList"
})
}));
c.stop();

const componentOptions = componentDetails.filter((req) => {
const c = req.data.data;
return c.diffStatus !== "None";
}).map((req) => {
const c = req.data.data;
return {
value: c.id,
label: c.name,
}
});

if (componentOptions.length === 0) {
p.outro(`${color.bgBlack(color.redBright("There are no modifications on this simulated change set."))}`);
p.outro("Goodbye!");
process.exit(0);
}
componentOptions.unshift({value: EXIT, label: "[quit]"})

while (true) {
const componentId = await p.select({
message: "Choose a modified component to review:",
options: componentOptions,
});
if (p.isCancel(componentId)) {
p.cancel("Cancelled, exiting...")
process.exit(0);
}
if (componentId === EXIT) break;

const diff = await mvSetsApi.get({
workspaceId,
changeSetId,
entityId: componentId,
kind: "ComponentDiff"
});

p.outro(`Here is what changed:`)
console.log(JSON.stringify(diff.data.data, undefined, 2));
}

p.outro("Goodbye");
}
7 changes: 6 additions & 1 deletion lib/luminork-server/src/service/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod components;
mod debug_funcs;
mod funcs;
mod management_funcs;
mod mv;
mod schemas;
mod search;
mod secrets;
Expand Down Expand Up @@ -132,6 +133,7 @@ pub use management_funcs::{
ManagementFuncsResult,
get_management_func_run_state::GetManagementFuncJobStateV1Response,
};
pub use mv::get::MvResponse;
pub use schemas::{
DetachFuncBindingV1Response,
GetSchemaV1Response,
Expand Down Expand Up @@ -266,6 +268,7 @@ pub use crate::api_types::func_run::v1::{
secrets::update_secret::update_secret,
secrets::get_secrets::get_secrets,
search::search,
mv::get::get,
),
components(
schemas(
Expand Down Expand Up @@ -364,6 +367,7 @@ pub use crate::api_types::func_run::v1::{
ExecDebugFuncV1Request,
ExecDebugFuncV1Response,
GetDebugFuncJobStateV1Response,
MvResponse,
)
),
tags(
Expand All @@ -375,7 +379,8 @@ pub use crate::api_types::func_run::v1::{
(name = "secrets", description = "Secret management endpoints"),
(name = "funcs", description = "Functions management endpoints"),
(name = "debug_funcs", description = "Debug function endpoints"),
(name = "management_funcs", description = "Management functions endpoints")
(name = "management_funcs", description = "Management functions endpoints"),
(name = "mv", description = "Materialized view endpoints"),
)
)]
pub struct V1ApiDoc;
Expand Down
87 changes: 87 additions & 0 deletions lib/luminork-server/src/service/v1/mv/get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use axum::{
Json,
extract::Query,
};
use sdf_extract::FriggStore;
use serde::{
Deserialize,
Serialize,
};
use si_frontend_mv_types::object::FrontendObject;
use utoipa::{
IntoParams,
ToSchema,
};

use super::{
MvError,
MvResult,
};
use crate::extract::change_set::ChangeSetDalContext;

#[derive(Deserialize, Serialize, ToSchema, IntoParams, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetParams {
#[schema(example = "01H9ZQD35JPMBGHH69BT0Q79VY", nullable = false, value_type = String)]
pub entity_id: String,
#[schema(example = "ComponentList", nullable = false, value_type = String)]
pub kind: String,
}

#[derive(Deserialize, Serialize, Debug, ToSchema, Clone)]
#[serde(rename_all = "camelCase")]
pub struct MvResponse {
#[schema(example = "ComponentList")]
pub kind: String,
#[schema(example = "")]
pub id: String,
#[schema(example = "")]
pub checksum: String,
#[schema(example = "{}")]
pub data: serde_json::Value,
}

#[utoipa::path(
get,
path = "/v1/w/{workspace_id}/change-sets/{change_set_id}/mv",
params(
("workspace_id" = String, Path, description = "Workspace identifier"),
("change_set_id" = String, Path, description = "Change Set identifier"),
GetParams,
),
tag = "mv",
summary = "Identifiers for a materialized view",
responses(
(status = 200, description = "Mv retrieved successfully", body = MvResponse),
(status = 404, description = "Mv not found"),
(status = 500, description = "Internal server error", body = crate::service::v1::common::ApiError)
)
)]
pub async fn get(
ChangeSetDalContext(ref ctx): ChangeSetDalContext,
Query(params): Query<GetParams>,
FriggStore(frigg): FriggStore,
) -> MvResult<Json<MvResponse>> {
let obj = frigg
.get_current_workspace_object(
ctx.workspace_pk()?,
ctx.change_set_id(),
&params.kind,
&params.entity_id,
)
.await?;
match obj {
Some(FrontendObject {
kind,
id,
checksum,
data,
}) => Ok(Json(MvResponse {
kind,
id,
checksum,
data,
})),
None => Err(MvError::NotFound(params.kind, params.entity_id)),
}
}
Loading