Commit c2ef26aa authored by Denys Mishunov's avatar Denys Mishunov Committed by Phil Hughes

Send attached paths to mutation

When creating a new snippet, to avoid attaching files to the user
object (isntead of the snippet itself), we should send the paths to
the mutation so that backend could move files to the correct place
when saving the new snippet. This is not an issue for the existing
snippets and update mutation
parent 008df56c
......@@ -137,18 +137,34 @@ export default {
this.onExistingSnippetFetched();
}
},
getAttachedFiles() {
const fileInputs = Array.from(this.$el.querySelectorAll('[name="files[]"]'));
return fileInputs.map(node => node.value);
},
createMutation() {
return {
mutation: CreateSnippetMutation,
variables: {
input: {
...this.apiData,
uploadedFiles: this.getAttachedFiles(),
projectPath: this.projectPath,
},
},
};
},
updateMutation() {
return {
mutation: UpdateSnippetMutation,
variables: {
input: this.apiData,
},
};
},
handleFormSubmit() {
this.isUpdating = true;
this.$apollo
.mutate({
mutation: this.newSnippet ? CreateSnippetMutation : UpdateSnippetMutation,
variables: {
input: {
...this.apiData,
projectPath: this.newSnippet ? this.projectPath : undefined,
},
},
})
.mutate(this.newSnippet ? this.createMutation() : this.updateMutation())
.then(({ data }) => {
const baseObj = this.newSnippet ? data?.createSnippet : data?.updateSnippet;
......@@ -176,6 +192,8 @@ export default {
<form
class="snippet-form js-requires-input js-quick-submit common-note-form"
:data-snippet-type="isProjectSnippet ? 'project' : 'personal'"
data-testid="snippet-edit-form"
@submit.prevent="handleFormSubmit"
>
<gl-loading-icon
v-if="isLoading"
......@@ -211,17 +229,17 @@ export default {
<form-footer-actions>
<template #prepend>
<gl-button
type="submit"
category="primary"
type="submit"
variant="success"
:disabled="updatePrevented"
data-qa-selector="submit_button"
@click.prevent="handleFormSubmit"
data-testid="snippet-submit-btn"
>{{ saveButtonLabel }}</gl-button
>
</template>
<template #append>
<gl-button data-testid="snippet-cancel-btn" :href="cancelButtonHref">{{
<gl-button type="cancel" data-testid="snippet-cancel-btn" :href="cancelButtonHref">{{
__('Cancel')
}}</gl-button>
</template>
......
......@@ -30,7 +30,7 @@ export default {
</script>
<template>
<div class="form-group js-description-input">
<label>{{ s__('Snippets|Description (optional)') }}</label>
<label for="snippet-description">{{ s__('Snippets|Description (optional)') }}</label>
<div class="js-collapsible-input">
<div class="js-collapsed" :class="{ 'd-none': value }">
<gl-form-input
......@@ -46,22 +46,26 @@ export default {
<markdown-field
class="js-expanded"
:class="{ 'd-none': !value }"
:add-spacing-classes="false"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
>
<textarea
slot="textarea"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-qa-selector="snippet_description_field"
data-supports-quick-actions="false"
:value="value"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
>
</textarea>
<template #textarea>
<textarea
id="snippet-description"
ref="textarea"
:value="value"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-qa-selector="snippet_description_field"
data-supports-quick-actions="false"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
>
</textarea>
</template>
</markdown-field>
</div>
</div>
......
---
title: Send information about attached files to the GraphQL mutation
merge_request: 34221
author:
type: fixed
......@@ -4,7 +4,9 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
<div
class="form-group js-description-input"
>
<label>
<label
for="snippet-description"
>
Description (optional)
</label>
......@@ -21,27 +23,67 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
/>
</div>
<markdown-field-stub
addspacingclasses="true"
canattachfile="true"
class="js-expanded"
enableautocomplete="true"
helppagepath=""
markdowndocspath="help/"
markdownpreviewpath="foo/"
note="[object Object]"
quickactionsdocspath=""
textareavalue=""
<div
class="js-vue-markdown-field md-area position-relative js-expanded gfm-form"
>
<textarea
aria-label="Description"
class="note-textarea js-gfm-input js-autosize markdown-area"
data-qa-selector="snippet_description_field"
data-supports-quick-actions="false"
dir="auto"
placeholder="Write a comment or drag your files here…"
<markdown-header-stub
linecontent=""
/>
</markdown-field-stub>
<div
class="md-write-holder"
>
<div
class="zen-backdrop div-dropzone-wrapper"
>
<div
class="div-dropzone js-invalid-dropzone"
>
<textarea
aria-label="Description"
class="note-textarea js-gfm-input js-autosize markdown-area"
data-qa-selector="snippet_description_field"
data-supports-quick-actions="false"
dir="auto"
id="snippet-description"
placeholder="Write a comment or drag your files here…"
style="overflow-x: hidden; word-wrap: break-word; overflow-y: hidden;"
/>
<div
class="div-dropzone-hover"
>
<i
class="fa fa-paperclip div-dropzone-icon"
/>
</div>
</div>
<a
aria-label="Leave zen mode"
class="zen-control zen-control-leave js-zen-leave gl-text-gray-700"
href="#"
>
<icon-stub
name="screen-normal"
size="16"
/>
</a>
<markdown-toolbar-stub
canattachfile="true"
markdowndocspath="help/"
quickactionsdocspath=""
/>
</div>
</div>
<div
class="js-vue-md-preview md md-preview-holder"
style="display: none;"
/>
<!---->
</div>
</div>
</div>
`;
......@@ -40,6 +40,9 @@ const newlyEditedSnippetUrl = 'http://foo.bar';
const apiError = { message: 'Ufff' };
const mutationError = 'Bummer';
const attachedFilePath1 = 'foo/bar';
const attachedFilePath2 = 'alpha/beta';
const defaultProps = {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
markdownPreviewPath: 'http://preview.foo.bar',
......@@ -120,8 +123,9 @@ describe('Snippet Edit app', () => {
wrapper.destroy();
});
const findSubmitButton = () => wrapper.find('[type=submit]');
const findSubmitButton = () => wrapper.find('[data-testid="snippet-submit-btn"]');
const findCancellButton = () => wrapper.find('[data-testid="snippet-cancel-btn"]');
const clickSubmitBtn = () => wrapper.find('[data-testid="snippet-edit-form"]').trigger('submit');
describe('rendering', () => {
it('renders loader while the query is in flight', () => {
......@@ -289,13 +293,15 @@ describe('Snippet Edit app', () => {
},
};
wrapper.vm.handleFormSubmit();
clickSubmitBtn();
expect(resolveMutate).toHaveBeenCalledWith(mutationPayload);
});
it('redirects to snippet view on successful mutation', () => {
createComponent();
wrapper.vm.handleFormSubmit();
clickSubmitBtn();
return waitForPromises().then(() => {
expect(redirectTo).toHaveBeenCalledWith(newlyEditedSnippetUrl);
});
......@@ -322,7 +328,8 @@ describe('Snippet Edit app', () => {
mutationRes: mutationTypes.RESOLVE_WITH_ERRORS,
});
wrapper.vm.handleFormSubmit();
clickSubmitBtn();
return waitForPromises().then(() => {
expect(redirectTo).not.toHaveBeenCalled();
expect(flashSpy).toHaveBeenCalledWith(mutationError);
......@@ -334,7 +341,9 @@ describe('Snippet Edit app', () => {
createComponent({
mutationRes: mutationTypes.REJECT,
});
wrapper.vm.handleFormSubmit();
clickSubmitBtn();
return waitForPromises().then(() => {
expect(redirectTo).not.toHaveBeenCalled();
expect(flashSpy).toHaveBeenCalledWith(apiError);
......@@ -354,12 +363,61 @@ describe('Snippet Edit app', () => {
},
mutationRes: mutationTypes.REJECT,
});
wrapper.vm.handleFormSubmit();
clickSubmitBtn();
return waitForPromises().then(() => {
expect(Flash).toHaveBeenCalledWith(expect.stringContaining(expectation));
});
},
);
});
describe('correctly includes attached files into the mutation', () => {
const createMutationPayload = expectation => {
return expect.objectContaining({
variables: {
input: expect.objectContaining({ uploadedFiles: expectation }),
},
});
};
const updateMutationPayload = () => {
return expect.objectContaining({
variables: {
input: expect.not.objectContaining({ uploadedFiles: expect.anything() }),
},
});
};
it.each`
paths | expectation
${[attachedFilePath1]} | ${[attachedFilePath1]}
${[attachedFilePath1, attachedFilePath2]} | ${[attachedFilePath1, attachedFilePath2]}
${[]} | ${[]}
`(`correctly sends paths for $paths.length files`, ({ paths, expectation }) => {
createComponent({
data: {
newSnippet: true,
},
});
const fixtures = paths.map(path => {
return path ? `<input name="files[]" value="${path}">` : undefined;
});
wrapper.vm.$el.innerHTML += fixtures.join('');
clickSubmitBtn();
expect(resolveMutate).toHaveBeenCalledWith(createMutationPayload(expectation));
});
it(`neither fails nor sends 'uploadedFiles' to update mutation`, () => {
createComponent();
clickSubmitBtn();
expect(resolveMutate).toHaveBeenCalledWith(updateMutationPayload());
});
});
});
});
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { shallowMount } from '@vue/test-utils';
describe('Snippet Description Edit component', () => {
......@@ -15,6 +16,9 @@ describe('Snippet Description Edit component', () => {
markdownPreviewPath,
markdownDocsPath,
},
stubs: {
MarkdownField,
},
});
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment