-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
71 changed files
with
3,770 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
@import "theme/blue.scss"; | ||
@import "~@fortawesome/fontawesome-free/scss/_variables"; | ||
|
||
.ui-form-element { | ||
margin-top: $margin-v * 0.25; | ||
margin-bottom: $margin-v * 0.5; | ||
overflow: visible; | ||
clear: both; | ||
|
||
.ui-form-title { | ||
word-wrap: break-word; | ||
font-weight: bold; | ||
|
||
.ui-form-title-message { | ||
font-size: $font-size-base * 0.7; | ||
font-weight: 300; | ||
vertical-align: text-top; | ||
color: $text-light; | ||
cursor: default; | ||
} | ||
|
||
.ui-form-title-star { | ||
color: $text-light; | ||
font-weight: 300; | ||
cursor: default; | ||
} | ||
|
||
.warning { | ||
color: $brand-danger; | ||
} | ||
} | ||
|
||
.ui-form-field { | ||
position: relative; | ||
margin-top: $margin-v * 0.25; | ||
} | ||
|
||
&:deep(.ui-form-collapsible-icon), | ||
&:deep(.ui-form-connected-icon) { | ||
border: none; | ||
background: none; | ||
padding: 0; | ||
line-height: 1; | ||
font-size: 1.2em; | ||
|
||
&:hover { | ||
color: $brand-info; | ||
} | ||
|
||
&:focus { | ||
color: $brand-primary; | ||
} | ||
|
||
&:active { | ||
background: none; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
client/src/components/ObjectStore/Instances/CreateForm.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { mount } from "@vue/test-utils"; | ||
import flushPromises from "flush-promises"; | ||
import { getLocalVue } from "tests/jest/helpers"; | ||
|
||
import { mockFetcher } from "@/api/schema/__mocks__"; | ||
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types"; | ||
|
||
import CreateForm from "./CreateForm.vue"; | ||
|
||
jest.mock("@/api/schema"); | ||
|
||
const FAKE_OBJECT_STORE = "A fake object store"; | ||
|
||
const localVue = getLocalVue(true); | ||
|
||
const STANDARD_TEMPLATE: ObjectStoreTemplateSummary = { | ||
type: "s3", | ||
name: "moo", | ||
description: null, | ||
variables: [ | ||
{ | ||
name: "myvar", | ||
type: "string", | ||
help: "*myvar help*", | ||
}, | ||
], | ||
secrets: [ | ||
{ | ||
name: "mysecret", | ||
help: "**mysecret help**", | ||
}, | ||
], | ||
id: "moo", | ||
version: 0, | ||
badges: [], | ||
}; | ||
|
||
const SIMPLE_TEMPLATE: ObjectStoreTemplateSummary = { | ||
type: "s3", | ||
name: "moo", | ||
description: null, | ||
variables: [ | ||
{ | ||
name: "myvar", | ||
type: "string", | ||
help: "*myvar help*", | ||
}, | ||
], | ||
secrets: [ | ||
{ | ||
name: "mysecret", | ||
help: "**mysecret help**", | ||
}, | ||
], | ||
id: "moo", | ||
version: 0, | ||
badges: [], | ||
}; | ||
|
||
describe("CreateForm", () => { | ||
it("should render a form with admin markdown converted to HTML in help", async () => { | ||
const wrapper = mount(CreateForm, { | ||
propsData: { | ||
template: STANDARD_TEMPLATE, | ||
}, | ||
localVue, | ||
}); | ||
await flushPromises(); | ||
|
||
const varFormEl = wrapper.find("#form-element-myvar"); | ||
expect(varFormEl).toBeTruthy(); | ||
expect(varFormEl.html()).toContain("<em>myvar help</em>"); | ||
|
||
const secretFormEl = wrapper.find("#form-element-mysecret"); | ||
expect(secretFormEl).toBeTruthy(); | ||
expect(secretFormEl.html()).toContain("<strong>mysecret help</strong>"); | ||
}); | ||
|
||
it("should post to create a new object store on submit", async () => { | ||
const wrapper = mount(CreateForm, { | ||
propsData: { | ||
template: SIMPLE_TEMPLATE, | ||
}, | ||
localVue, | ||
}); | ||
mockFetcher.path("/api/object_store_instances").method("post").mock({ data: FAKE_OBJECT_STORE }); | ||
await flushPromises(); | ||
const nameForElement = wrapper.find("#form-element-_meta_name"); | ||
nameForElement.find("input").setValue("My New Name"); | ||
const submitElement = wrapper.find("#submit"); | ||
submitElement.trigger("click"); | ||
await flushPromises(); | ||
const emitted = wrapper.emitted("created") || []; | ||
expect(emitted).toHaveLength(1); | ||
expect(emitted[0][0]).toBe(FAKE_OBJECT_STORE); | ||
}); | ||
|
||
it("should indicate an error on failure", async () => { | ||
const wrapper = mount(CreateForm, { | ||
propsData: { | ||
template: SIMPLE_TEMPLATE, | ||
}, | ||
localVue, | ||
}); | ||
mockFetcher | ||
.path("/api/object_store_instances") | ||
.method("post") | ||
.mock(() => { | ||
throw Error("Error creating this"); | ||
}); | ||
await flushPromises(); | ||
const nameForElement = wrapper.find("#form-element-_meta_name"); | ||
nameForElement.find("input").setValue("My New Name"); | ||
const submitElement = wrapper.find("#submit"); | ||
expect(wrapper.find(".object-store-instance-creation-error").exists()).toBe(false); | ||
submitElement.trigger("click"); | ||
await flushPromises(); | ||
const emitted = wrapper.emitted("created") || []; | ||
expect(emitted).toHaveLength(0); | ||
const errorEl = wrapper.find(".object-store-instance-creation-error"); | ||
expect(errorEl.exists()).toBe(true); | ||
expect(errorEl.html()).toContain("Error creating this"); | ||
}); | ||
}); |
81 changes: 81 additions & 0 deletions
81
client/src/components/ObjectStore/Instances/CreateForm.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<script lang="ts" setup> | ||
import { computed, ref } from "vue"; | ||
import { create } from "@/components/ObjectStore/Instances/services"; | ||
import type { SecretData, UserConcreteObjectStore, VariableData } from "@/components/ObjectStore/Instances/types"; | ||
import { | ||
metadataFormEntryDescription, | ||
metadataFormEntryName, | ||
templateSecretFormEntry, | ||
templateVariableFormEntry, | ||
} from "@/components/ObjectStore/Instances/util"; | ||
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types"; | ||
import { errorMessageAsString } from "@/utils/simple-error"; | ||
import InstanceForm from "./InstanceForm.vue"; | ||
interface CreateFormProps { | ||
template: ObjectStoreTemplateSummary; | ||
} | ||
const error = ref<string | null>(null); | ||
const props = defineProps<CreateFormProps>(); | ||
const title = "Create a new object store for your data"; | ||
const submitTitle = "Submit"; | ||
const inputs = computed(() => { | ||
const form = []; | ||
const variables = props.template.variables ?? []; | ||
const secrets = props.template.secrets ?? []; | ||
form.push(metadataFormEntryName()); | ||
form.push(metadataFormEntryDescription()); | ||
for (const variable of variables) { | ||
form.push(templateVariableFormEntry(variable, undefined)); | ||
} | ||
for (const secret of secrets) { | ||
form.push(templateSecretFormEntry(secret)); | ||
} | ||
return form; | ||
}); | ||
async function onSubmit(formData: any) { | ||
const variables = props.template.variables ?? []; | ||
const secrets = props.template.secrets ?? []; | ||
const variableData: VariableData = {}; | ||
const secretData: SecretData = {}; | ||
for (const variable of variables) { | ||
variableData[variable.name] = formData[variable.name]; | ||
} | ||
for (const secret of secrets) { | ||
secretData[secret.name] = formData[secret.name]; | ||
} | ||
const name: string = formData._meta_name; | ||
const description: string = formData._meta_description; | ||
const payload = { | ||
name: name, | ||
description: description, | ||
secrets: secretData, | ||
variables: variableData, | ||
template_id: props.template.id, | ||
template_version: props.template.version ?? 0, | ||
}; | ||
try { | ||
const { data: objectStore } = await create(payload); | ||
emit("created", objectStore); | ||
} catch (e) { | ||
error.value = errorMessageAsString(e); | ||
return; | ||
} | ||
} | ||
const emit = defineEmits<{ | ||
(e: "created", objectStore: UserConcreteObjectStore): void; | ||
}>(); | ||
</script> | ||
<template> | ||
<div> | ||
<b-alert v-if="error" variant="danger" class="object-store-instance-creation-error" show> | ||
{{ error }} | ||
</b-alert> | ||
<InstanceForm :inputs="inputs" :title="title" :submit-title="submitTitle" @onSubmit="onSubmit" /> | ||
</div> | ||
</template> |
Oops, something went wrong.