-
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
67 changed files
with
3,390 additions
and
134 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
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.25; | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<script setup lang="ts"> | ||
import { computed } from "vue"; | ||
import { adminMarkup } from "./adminConfig"; | ||
interface Props { | ||
markdown: string; | ||
} | ||
const props = defineProps<Props>(); | ||
const adminMarkdownHtml = computed(() => adminMarkup(props.markdown || "")); | ||
</script> | ||
<template> | ||
<!-- Disable v-html warning because this component is only used | ||
for admin defined content - HTML should be trusted in this | ||
context. This component should never be used to render user | ||
generated or defined content. | ||
--> | ||
<!-- eslint-disable-next-line vue/no-v-html --> | ||
<div v-html="adminMarkdownHtml" /> | ||
</template> |
123 changes: 123 additions & 0 deletions
123
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,123 @@ | ||
import { mount } from "@vue/test-utils"; | ||
import { getLocalVue } from "tests/jest/helpers"; | ||
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types"; | ||
import flushPromises from "flush-promises"; | ||
import { mockFetcher } from "@/schema/__mocks__"; | ||
|
||
jest.mock("@/schema"); | ||
|
||
const FAKE_OBJECT_STORE = "A fake object store"; | ||
|
||
const localVue = getLocalVue(true); | ||
|
||
import CreateForm from "./CreateForm.vue"; | ||
|
||
const STANDARD_TEMPLATE: ObjectStoreTemplateSummary = { | ||
type: "s3", | ||
name: "moo", | ||
description: undefined, | ||
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: undefined, | ||
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"); | ||
}); | ||
}); |
79 changes: 79 additions & 0 deletions
79
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,79 @@ | ||
<script lang="ts" setup> | ||
import { computed, ref } from "vue"; | ||
import type { ObjectStoreTemplateSummary } from "@/components/ObjectStore/Templates/types"; | ||
import type { SecretData, VariableData, UserConcreteObjectStore } from "@/components/ObjectStore/Instances/types"; | ||
import InstanceForm from "./InstanceForm.vue"; | ||
import { create } from "@/components/ObjectStore/Instances/services"; | ||
import { errorMessageAsString } from "@/utils/simple-error"; | ||
import { | ||
metadataFormEntryDescription, | ||
metadataFormEntryName, | ||
templateVariableFormEntry, | ||
templateSecretFormEntry, | ||
} from "@/components/ObjectStore/Instances/util"; | ||
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> | ||
<instance-form :inputs="inputs" :title="title" :submit-title="submitTitle" @onSubmit="onSubmit" /> | ||
</div> | ||
</template> |
33 changes: 33 additions & 0 deletions
33
client/src/components/ObjectStore/Instances/CreateInstance.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,33 @@ | ||
<script setup lang="ts"> | ||
import { computed } from "vue"; | ||
import LoadingSpan from "@/components/LoadingSpan.vue"; | ||
import CreateForm from "./CreateForm.vue"; | ||
import type { UserConcreteObjectStore } from "./types"; | ||
interface Props { | ||
templateId: string; | ||
} | ||
import { useInstanceRouting } from "./routing"; | ||
import { useObjectStoreTemplatesStore } from "@/stores/objectStoreTemplatesStore"; | ||
const objectStoreTemplatesStore = useObjectStoreTemplatesStore(); | ||
objectStoreTemplatesStore.fetchTemplates(); | ||
const { goToIndex } = useInstanceRouting(); | ||
const props = defineProps<Props>(); | ||
const template = computed(() => objectStoreTemplatesStore.getLatestTemplate(props.templateId)); | ||
async function onCreated(objectStore: UserConcreteObjectStore) { | ||
const message = `Created object store ${objectStore.name}`; | ||
goToIndex({ message }); | ||
} | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<loading-span v-if="!template" message="Loading object store templates" /> | ||
<create-form v-else :template="template" @created="onCreated"></create-form> | ||
</div> | ||
</template> |
Oops, something went wrong.