diff --git a/app/web/src/components/AssetDetailsPanel.vue b/app/web/src/components/AssetDetailsPanel.vue
index 6b69c42cf1..9f183ea489 100644
--- a/app/web/src/components/AssetDetailsPanel.vue
+++ b/app/web/src/components/AssetDetailsPanel.vue
@@ -25,9 +25,15 @@
tone="neutral"
icon="clipboard-copy"
size="md"
- @click="cloneAsset"
+ @click="() => cloneAssetModalRef?.modal?.open()"
/>
+
>();
const openAttachModal = (warning: { kind?: FuncKind; funcId?: FuncId }) => {
if (!warning.kind) return;
@@ -232,10 +240,11 @@ const closeHandler = () => {
assetStore.executeAssetTaskId = undefined;
};
-const cloneAsset = async () => {
+const cloneAsset = async (name: string) => {
if (editingAsset.value?.id) {
- const result = await assetStore.CLONE_ASSET(editingAsset.value.id);
+ const result = await assetStore.CLONE_ASSET(editingAsset.value.id, name);
if (result.result.success) {
+ cloneAssetModalRef.value?.modal?.close();
await assetStore.setAssetSelection(result.result.data.id);
}
}
diff --git a/app/web/src/components/AssetListPanel.vue b/app/web/src/components/AssetListPanel.vue
index 5b22641ae1..22a356cfb3 100644
--- a/app/web/src/components/AssetListPanel.vue
+++ b/app/web/src/components/AssetListPanel.vue
@@ -24,7 +24,7 @@
tooltip="New Asset"
tooltipPlacement="top"
loadingTooltip="Creating Asset..."
- @click="newAsset"
+ @click="() => newAssetModalRef?.modal?.open()"
/>
+ newAsset(name)"
+ />
>();
const exportSuccessModalRef = ref>();
+const newAssetModalRef = ref>();
const contributeLoadingTexts = [
"Engaging Photon Torpedos...",
@@ -219,10 +227,17 @@ const categoryColor = (category: string) => {
return "#000";
};
-const newAsset = async () => {
- const result = await assetStore.CREATE_ASSET(assetStore.createNewAsset());
+const newAsset = async (newAssetName: string) => {
+ const result = await assetStore.CREATE_ASSET(
+ assetStore.createNewAsset(newAssetName),
+ );
if (result.result.success) {
assetStore.setAssetSelection(result.result.data.id);
+ newAssetModalRef.value?.modal?.close();
+ } else if (result.result.statusCode === 409) {
+ if (newAssetModalRef.value) {
+ newAssetModalRef.value.setError("That name is already in use");
+ }
}
};
diff --git a/app/web/src/components/AssetNameModal.vue b/app/web/src/components/AssetNameModal.vue
new file mode 100644
index 0000000000..38f39331b6
--- /dev/null
+++ b/app/web/src/components/AssetNameModal.vue
@@ -0,0 +1,44 @@
+
+
+
+ {{ props.buttonLabel }}
+
+
+
+
diff --git a/app/web/src/store/asset.store.ts b/app/web/src/store/asset.store.ts
index 63769c0718..71bc0bf468 100644
--- a/app/web/src/store/asset.store.ts
+++ b/app/web/src/store/asset.store.ts
@@ -103,7 +103,7 @@ export type AssetCreateRequest = Omit<
AssetSaveRequest,
"id" | "definition" | "variantExists"
>;
-export type AssetCloneRequest = Visibility & { id: AssetId };
+export type AssetCloneRequest = Visibility & { id: AssetId; name: string };
export const assetDisplayName = (asset: Asset | AssetListEntry) =>
(asset.displayName ?? "").length === 0 ? asset.name : asset.displayName;
@@ -298,11 +298,13 @@ export const useAssetStore = () => {
])}`;
},
- createNewAsset(): Asset {
+ createNewAsset(name?: string): Asset {
+ name = name || `new asset ${Math.floor(Math.random() * 10000)}`;
return {
id: nilId(),
defaultSchemaVariantId: "",
- name: `new asset ${Math.floor(Math.random() * 10000)}`,
+ name,
+ displayName: name,
code: "",
color: this.generateMockColor(),
description: "",
@@ -339,7 +341,7 @@ export const useAssetStore = () => {
});
},
- async CLONE_ASSET(assetId: AssetId) {
+ async CLONE_ASSET(assetId: AssetId, name: string) {
if (changeSetsStore.creatingChangeSet)
throw new Error("race, wait until the change set is created");
if (changeSetsStore.headSelected)
@@ -355,6 +357,7 @@ export const useAssetStore = () => {
params: {
...visibility,
id: assetId,
+ name,
},
});
},
diff --git a/lib/dal/src/schema.rs b/lib/dal/src/schema.rs
index a042bd8ce3..91b172c026 100644
--- a/lib/dal/src/schema.rs
+++ b/lib/dal/src/schema.rs
@@ -473,4 +473,8 @@ impl Schema {
let funcs = Func::list_from_ids(ctx, func_ids.as_slice()).await?;
Ok(funcs)
}
+
+ pub async fn is_name_taken(ctx: &DalContext, name: &String) -> SchemaResult {
+ Ok(Self::list(ctx).await?.iter().any(|s| s.name.eq(name)))
+ }
}
diff --git a/lib/dal/src/schema/variant/authoring.rs b/lib/dal/src/schema/variant/authoring.rs
index cd560c7f72..1f94d73402 100644
--- a/lib/dal/src/schema/variant/authoring.rs
+++ b/lib/dal/src/schema/variant/authoring.rs
@@ -20,9 +20,8 @@ use crate::pkg::import::import_only_new_funcs;
use crate::pkg::{import_pkg_from_pkg, PkgError};
use crate::schema::variant::{SchemaVariantJson, SchemaVariantMetadataJson};
use crate::{
- generate_unique_id, pkg, ComponentType, DalContext, Func, FuncBackendKind,
- FuncBackendResponseType, FuncError, FuncId, Schema, SchemaError, SchemaVariant,
- SchemaVariantError, SchemaVariantId,
+ pkg, ComponentType, DalContext, Func, FuncBackendKind, FuncBackendResponseType, FuncError,
+ FuncId, Schema, SchemaError, SchemaVariant, SchemaVariantError, SchemaVariantId,
};
#[allow(missing_docs)]
@@ -82,7 +81,7 @@ impl VariantAuthoringClient {
/// Creates a [`SchemaVariant`] and returns the [result](SchemaVariant).
#[instrument(name = "variant.authoring.create_variant", level = "info", skip_all)]
#[allow(clippy::too_many_arguments)]
- pub async fn create_variant(
+ pub async fn create_schema_and_variant(
ctx: &DalContext,
name: impl Into,
display_name: Option,
@@ -112,7 +111,7 @@ impl VariantAuthoringClient {
let definition = execute_asset_func(ctx, &asset_func).await?;
let metadata = SchemaVariantMetadataJson {
- name,
+ schema_name: name,
menu_name: display_name.clone(),
category: category.into(),
color: color.into(),
@@ -159,23 +158,22 @@ impl VariantAuthoringClient {
pub async fn clone_variant(
ctx: &DalContext,
schema_variant_id: SchemaVariantId,
+ name: String,
) -> VariantAuthoringResult<(SchemaVariant, Schema)> {
- println!("clone variant");
let variant = SchemaVariant::get_by_id(ctx, schema_variant_id).await?;
let schema = variant.schema(ctx).await?;
- let new_name = format!("{} Clone {}", schema.name(), generate_unique_id(4));
- let menu_name = variant.display_name().map(|mn| format!("{mn} Clone"));
+ let display_name = variant.display_name().map(|dn| format!("{dn} Clone"));
if let Some(asset_func_id) = variant.asset_func_id() {
let old_func = Func::get_by_id_or_error(ctx, asset_func_id).await?;
- let cloned_func = old_func.duplicate(ctx, new_name.clone()).await?;
+ let cloned_func = old_func.duplicate(ctx, name.clone()).await?;
let cloned_func_spec = build_asset_func_spec(&cloned_func)?;
let definition = execute_asset_func(ctx, &cloned_func).await?;
let metadata = SchemaVariantMetadataJson {
- name: new_name.clone(),
- menu_name: menu_name.clone(),
+ schema_name: name,
+ menu_name: display_name.clone(),
category: variant.category().to_string(),
color: variant.get_color(ctx).await?,
component_type: variant.component_type(),
@@ -331,7 +329,7 @@ impl VariantAuthoringClient {
let asset_func_spec = build_asset_func_spec(&asset_func)?;
let definition = execute_asset_func(ctx, &asset_func).await?;
let metadata = SchemaVariantMetadataJson {
- name: name.clone(),
+ schema_name: name.clone(),
menu_name: menu_name.clone(),
category: category.clone(),
color: color.clone(),
@@ -350,10 +348,10 @@ impl VariantAuthoringClient {
)
.await?;
- let schema_spec = metadata.to_spec(new_variant_spec)?;
+ let schema_spec = metadata.to_schema_spec(new_variant_spec)?;
//TODO @stack72 - figure out how we get the current user in this!
let pkg_spec = PkgSpec::builder()
- .name(&metadata.name)
+ .name(&metadata.schema_name)
.created_by("sally@systeminit.com")
.funcs(variant_funcs.clone())
.func(asset_func_spec)
@@ -475,7 +473,7 @@ impl VariantAuthoringClient {
let definition = execute_asset_func(ctx, &new_asset_func).await?;
let metadata = SchemaVariantMetadataJson {
- name: name.clone(),
+ schema_name: name.clone(),
menu_name: menu_name.clone(),
category: category.into(),
color: color.into(),
@@ -494,11 +492,11 @@ impl VariantAuthoringClient {
)
.await?;
- let schema_spec = metadata.to_spec(new_variant_spec)?;
+ let schema_spec = metadata.to_schema_spec(new_variant_spec)?;
//TODO @stack72 - figure out how we get the current user in this!
let pkg_spec = PkgSpec::builder()
- .name(&metadata.name)
+ .name(&metadata.schema_name)
.created_by("sally@systeminit.com")
.funcs(variant_funcs.clone())
.func(asset_func_spec)
@@ -746,9 +744,9 @@ fn build_pkg_spec_for_variant(
&identity_func_spec.unique_id,
&asset_func_spec.unique_id,
)?;
- let schema_spec = metadata.to_spec(variant_spec)?;
+ let schema_spec = metadata.to_schema_spec(variant_spec)?;
Ok(PkgSpec::builder()
- .name(metadata.clone().name)
+ .name(metadata.clone().schema_name)
.created_by(user_email)
.func(identity_func_spec)
.func(asset_func_spec.clone())
diff --git a/lib/dal/src/schema/variant/json.rs b/lib/dal/src/schema/variant/json.rs
index 56e011e58c..b56629e33a 100644
--- a/lib/dal/src/schema/variant/json.rs
+++ b/lib/dal/src/schema/variant/json.rs
@@ -16,7 +16,7 @@ use crate::{ComponentType, PropKind, SchemaVariantError, SocketArity};
pub struct SchemaVariantMetadataJson {
/// Name for this variant. Actually, this is the name for this [`Schema`](crate::Schema), we're
/// punting on the issue of multiple variants for the moment.
- pub name: String,
+ pub schema_name: String,
/// Override for the UI name for this schema
#[serde(alias = "menu_name")]
pub menu_name: Option,
@@ -31,11 +31,11 @@ pub struct SchemaVariantMetadataJson {
}
impl SchemaVariantMetadataJson {
- pub fn to_spec(&self, variant: SchemaVariantSpec) -> SchemaVariantResult {
+ pub fn to_schema_spec(&self, variant: SchemaVariantSpec) -> SchemaVariantResult {
let mut builder = SchemaSpec::builder();
- builder.name(&self.name);
+ builder.name(&self.schema_name);
let mut data_builder = SchemaSpecData::builder();
- data_builder.name(&self.name);
+ data_builder.name(&self.schema_name);
data_builder.category(&self.category);
if let Some(menu_name) = &self.menu_name {
data_builder.category_name(menu_name.as_str());
@@ -168,7 +168,7 @@ impl SchemaVariantJson {
});
let metadata = SchemaVariantMetadataJson {
- name: schema_spec.name,
+ schema_name: schema_spec.name,
menu_name: schema_data.category_name,
category: schema_data.category,
color: variant_spec_data
diff --git a/lib/dal/tests/integration_test/component/upgrade.rs b/lib/dal/tests/integration_test/component/upgrade.rs
index de22730ae2..0ff48c0532 100644
--- a/lib/dal/tests/integration_test/component/upgrade.rs
+++ b/lib/dal/tests/integration_test/component/upgrade.rs
@@ -21,7 +21,7 @@ async fn upgrade_component(ctx: &mut DalContext) {
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let variant_zero = VariantAuthoringClient::create_variant(
+ let variant_zero = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
diff --git a/lib/dal/tests/integration_test/func/authoring/create_func.rs b/lib/dal/tests/integration_test/func/authoring/create_func.rs
index a290862cd3..03e82686fd 100644
--- a/lib/dal/tests/integration_test/func/authoring/create_func.rs
+++ b/lib/dal/tests/integration_test/func/authoring/create_func.rs
@@ -540,7 +540,7 @@ async fn create_qualification_and_code_gen_with_existing_component(ctx: &mut Dal
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let variant_zero = VariantAuthoringClient::create_variant(
+ let variant_zero = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
diff --git a/lib/dal/tests/integration_test/pkg/mod.rs b/lib/dal/tests/integration_test/pkg/mod.rs
index 1dbcca401e..9a0396e724 100644
--- a/lib/dal/tests/integration_test/pkg/mod.rs
+++ b/lib/dal/tests/integration_test/pkg/mod.rs
@@ -14,7 +14,7 @@ async fn import_pkg_from_pkg_set_latest_default(ctx: &mut DalContext) {
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let variant = VariantAuthoringClient::create_variant(
+ let variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
diff --git a/lib/dal/tests/integration_test/schema/variant/authoring/clone_variant.rs b/lib/dal/tests/integration_test/schema/variant/authoring/clone_variant.rs
index e035948f91..bf24f4bdcb 100644
--- a/lib/dal/tests/integration_test/schema/variant/authoring/clone_variant.rs
+++ b/lib/dal/tests/integration_test/schema/variant/authoring/clone_variant.rs
@@ -32,6 +32,7 @@ async fn clone_variant(ctx: &mut DalContext) {
let (new_schema_variant, _) = VariantAuthoringClient::clone_variant(
ctx,
default_schema_variant.expect("unable to get the schema variant id from the option"),
+ existing_schema.name().to_string(),
)
.await
.expect("unable to clone the schema variant");
diff --git a/lib/dal/tests/integration_test/schema/variant/authoring/create_variant.rs b/lib/dal/tests/integration_test/schema/variant/authoring/create_variant.rs
index f51bb78b08..b448ff4cbc 100644
--- a/lib/dal/tests/integration_test/schema/variant/authoring/create_variant.rs
+++ b/lib/dal/tests/integration_test/schema/variant/authoring/create_variant.rs
@@ -18,7 +18,7 @@ async fn create_variant(ctx: &mut DalContext) {
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let variant = VariantAuthoringClient::create_variant(
+ let variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
diff --git a/lib/dal/tests/integration_test/schema/variant/authoring/save_variant.rs b/lib/dal/tests/integration_test/schema/variant/authoring/save_variant.rs
index c399a4b62a..a769a57fac 100644
--- a/lib/dal/tests/integration_test/schema/variant/authoring/save_variant.rs
+++ b/lib/dal/tests/integration_test/schema/variant/authoring/save_variant.rs
@@ -18,7 +18,7 @@ async fn save_variant(ctx: &mut DalContext) {
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let variant = VariantAuthoringClient::create_variant(
+ let variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
diff --git a/lib/dal/tests/integration_test/schema/variant/authoring/update_variant.rs b/lib/dal/tests/integration_test/schema/variant/authoring/update_variant.rs
index ac39f393e6..295f007b3a 100644
--- a/lib/dal/tests/integration_test/schema/variant/authoring/update_variant.rs
+++ b/lib/dal/tests/integration_test/schema/variant/authoring/update_variant.rs
@@ -24,7 +24,7 @@ async fn update_variant(ctx: &mut DalContext) {
let link = None;
let category = "Integration Tests".to_string();
let color = "#00b0b0".to_string();
- let my_first_variant = VariantAuthoringClient::create_variant(
+ let my_first_variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
asset_name.clone(),
display_name.clone(),
@@ -138,7 +138,7 @@ async fn update_variant(ctx: &mut DalContext) {
#[test]
async fn update_variant_with_new_prototypes_for_new_func(ctx: &mut DalContext) {
- let first_variant = VariantAuthoringClient::create_variant(
+ let first_variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
"helix",
None,
@@ -309,7 +309,7 @@ async fn update_variant_with_new_prototypes_for_new_func(ctx: &mut DalContext) {
#[test]
async fn update_variant_with_leaf_func(ctx: &mut DalContext) {
- let schema_variant = VariantAuthoringClient::create_variant(
+ let schema_variant = VariantAuthoringClient::create_schema_and_variant(
ctx,
"helix",
None,
diff --git a/lib/dal/tests/integration_test/secret/with_schema_variant_authoring.rs b/lib/dal/tests/integration_test/secret/with_schema_variant_authoring.rs
index dec602c037..fd1a894c38 100644
--- a/lib/dal/tests/integration_test/secret/with_schema_variant_authoring.rs
+++ b/lib/dal/tests/integration_test/secret/with_schema_variant_authoring.rs
@@ -10,7 +10,7 @@ use dal_test::test;
#[test]
async fn existing_code_gen_func_using_secrets_for_new_schema_variant(ctx: &mut DalContext) {
// Create a new schema variant and commit.
- let schema_variant = VariantAuthoringClient::create_variant(
+ let schema_variant = VariantAuthoringClient::create_schema_and_variant(
ctx, "ergo sum", None, None, None, "bungie", "#00b0b0",
)
.await
diff --git a/lib/sdf-server/src/server/service/variant/clone_variant.rs b/lib/sdf-server/src/server/service/variant/clone_variant.rs
index 4bbb1eaea6..429d1f8d91 100644
--- a/lib/sdf-server/src/server/service/variant/clone_variant.rs
+++ b/lib/sdf-server/src/server/service/variant/clone_variant.rs
@@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "camelCase")]
pub struct CloneVariantRequest {
pub id: SchemaId,
+ pub name: String,
#[serde(flatten)]
pub visibility: Visibility,
}
@@ -31,6 +32,12 @@ pub async fn clone_variant(
) -> SchemaVariantResult {
let mut ctx = builder.build(request_ctx.build(request.visibility)).await?;
+ if Schema::is_name_taken(&ctx, &request.name).await? {
+ return Ok(axum::response::Response::builder()
+ .status(409)
+ .body("schema name already taken".to_string())?);
+ }
+
let force_change_set_id = ChangeSet::force_new(&mut ctx).await?;
let schema = Schema::get_by_id(&ctx, request.id).await?;
@@ -38,8 +45,12 @@ pub async fn clone_variant(
SchemaVariantError::NoDefaultSchemaVariantFoundForSchema(schema.id()),
)?;
- let (cloned_schema_variant, schema) =
- VariantAuthoringClient::clone_variant(&ctx, default_schema_variant_id).await?;
+ let (cloned_schema_variant, schema) = VariantAuthoringClient::clone_variant(
+ &ctx,
+ default_schema_variant_id,
+ request.name.clone(),
+ )
+ .await?;
track(
&posthog_client,
@@ -47,7 +58,7 @@ pub async fn clone_variant(
&original_uri,
"clone_variant",
serde_json::json!({
- "variant_name": schema.name(),
+ "variant_name": request.name,
"variant_category": cloned_schema_variant.category(),
"variant_menu_name": cloned_schema_variant.display_name(),
"variant_id": cloned_schema_variant.id(),
diff --git a/lib/sdf-server/src/server/service/variant/create_variant.rs b/lib/sdf-server/src/server/service/variant/create_variant.rs
index 04276d4619..29ee1af6c2 100644
--- a/lib/sdf-server/src/server/service/variant/create_variant.rs
+++ b/lib/sdf-server/src/server/service/variant/create_variant.rs
@@ -3,7 +3,7 @@ use axum::{response::IntoResponse, Json};
use serde::{Deserialize, Serialize};
use dal::schema::variant::authoring::VariantAuthoringClient;
-use dal::{ChangeSet, SchemaId, Visibility, WsEvent};
+use dal::{ChangeSet, Schema, SchemaId, Visibility, WsEvent};
use crate::server::extract::{AccessBuilder, HandlerContext, PosthogClient};
use crate::server::tracking::track;
@@ -26,7 +26,6 @@ pub struct CreateVariantRequest {
#[serde(rename_all = "camelCase")]
pub struct CreateVariantResponse {
pub id: SchemaId,
- pub success: bool,
}
pub async fn create_variant(
@@ -38,9 +37,15 @@ pub async fn create_variant(
) -> SchemaVariantResult {
let mut ctx = builder.build(request_ctx.build(request.visibility)).await?;
+ if Schema::is_name_taken(&ctx, &request.name).await? {
+ return Ok(axum::response::Response::builder()
+ .status(409)
+ .body("schema name already taken".to_string())?);
+ }
+
let force_change_set_id = ChangeSet::force_new(&mut ctx).await?;
- let created_schema_variant = VariantAuthoringClient::create_variant(
+ let created_schema_variant = VariantAuthoringClient::create_schema_and_variant(
&ctx,
request.name.clone(),
request.display_name.clone(),
@@ -87,6 +92,5 @@ pub async fn create_variant(
}
Ok(response.body(serde_json::to_string(&CreateVariantResponse {
id: schema.id(),
- success: true,
})?)?)
}
diff --git a/lib/vue-lib/src/pinia/pinia_api_tools.ts b/lib/vue-lib/src/pinia/pinia_api_tools.ts
index fbd0794374..8aa4a7a65c 100644
--- a/lib/vue-lib/src/pinia/pinia_api_tools.ts
+++ b/lib/vue-lib/src/pinia/pinia_api_tools.ts
@@ -106,7 +106,12 @@ export class ApiRequest<
// ie, checking success guarantees data is present
get result():
| { success: true; data: Response }
- | { success: false; err: Error; errBody?: any } {
+ | {
+ success: false;
+ err: Error;
+ errBody?: any;
+ statusCode?: number | undefined;
+ } {
/* eslint-disable @typescript-eslint/no-non-null-assertion */
if (this.rawSuccess === undefined)
throw new Error("You must await the request to access the result");
@@ -121,6 +126,7 @@ export class ApiRequest<
// the (json) body of the failed request, if applicable
...(this.rawResponseError instanceof AxiosError && {
errBody: this.rawResponseError.response?.data,
+ statusCode: this.rawResponseError.response?.status,
}),
};
}