From e228c109947388c3c30b83272a93b3263c01edfe Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:44 -0400 Subject: [PATCH 01/85] Add base presenter class This should reduce boilerplate and duplicated code. --- lib/presenters/base.js | 33 +++++++++++++++++++++++++++++++++ lib/presenters/field-key.js | 34 ++++++++++++++++------------------ lib/presenters/form.js | 36 ++++++++++++++++++++---------------- 3 files changed, 69 insertions(+), 34 deletions(-) create mode 100644 lib/presenters/base.js diff --git a/lib/presenters/base.js b/lib/presenters/base.js new file mode 100644 index 000000000..770aa101c --- /dev/null +++ b/lib/presenters/base.js @@ -0,0 +1,33 @@ +/* +Copyright 2017 ODK Central Developers +See the NOTICE file at the top-level directory of this distribution and at +https://github.com/opendatakit/central-frontend/blob/master/NOTICE. + +This file is part of ODK Central. It is subject to the license terms in +the LICENSE file found in the top-level directory of this distribution and at +https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, +including this file, may be copied, modified, propagated, or distributed +except according to the terms contained in the LICENSE file. +*/ +import Vue from 'vue'; + +class Base { + get key() { + if (this._key != null) return this._key; + this._key = Vue.prototype.$uniqueId(); + return this._key; + } +} + +export default (props) => { + const klass = class extends Base {}; + + // Add a getter for each property of the underlying data. + for (const name of props) { + Object.defineProperty(klass.prototype, name, { + get() { return this._data[name]; } + }); + } + + return klass; +}; diff --git a/lib/presenters/field-key.js b/lib/presenters/field-key.js index 047028af2..d2ec037ca 100644 --- a/lib/presenters/field-key.js +++ b/lib/presenters/field-key.js @@ -9,38 +9,36 @@ https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ -import Vue from 'vue'; import qrcode from 'qrcode-generator'; import { deflate } from 'pako/lib/deflate'; +import Base from './base'; + +const props = [ + 'id', + 'displayName', + 'token', + 'lastUsed', + 'createdBy', + 'createdAt' +]; + const QR_CODE_TYPE_NUMBER = 0; // This is the level used in Collect. const QR_CODE_ERROR_CORRECTION_LEVEL = 'L'; const QR_CODE_CELL_SIZE = 3; const QR_CODE_MARGIN = 0; -export default class FieldKey { - constructor(fieldKey) { - this._fieldKey = fieldKey; - } - - get id() { return this._fieldKey.id; } - get displayName() { return this._fieldKey.displayName; } - get token() { return this._fieldKey.token; } - get lastUsed() { return this._fieldKey.lastUsed; } - get createdBy() { return this._fieldKey.createdBy; } - get createdAt() { return this._fieldKey.createdAt; } - - get key() { - if (this._key != null) return this._key; - this._key = Vue.prototype.$uniqueId(); - return this._key; +export default class FieldKey extends Base(props) { + constructor(data) { + super(); + this._data = data; } qrCodeHtml() { if (this._qrCodeHtml != null) return this._qrCodeHtml; const code = qrcode(QR_CODE_TYPE_NUMBER, QR_CODE_ERROR_CORRECTION_LEVEL); - const url = `${window.location.origin}/api/v1/key/${this._fieldKey.token}`; + const url = `${window.location.origin}/api/v1/key/${this.token}`; // Collect requires the JSON to have 'general' and 'admin' keys, even if the // associated values are empty objects. const settings = { general: { server_url: url }, admin: {} }; diff --git a/lib/presenters/form.js b/lib/presenters/form.js index db961702f..5c4674254 100644 --- a/lib/presenters/form.js +++ b/lib/presenters/form.js @@ -9,24 +9,28 @@ https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. */ -export default class Form { - constructor(form) { - this._form = form; - } - - get xmlFormId() { return this._form.xmlFormId; } - get name() { return this._form.name; } - get version() { return this._form.version; } - get xml() { return this._form.xml; } - get hash() { return this._form.hash; } - get state() { return this._form.state; } - get createdBy() { return this._form.createdBy; } - get createdAt() { return this._form.createdAt; } - get updatedAt() { return this._form.updatedAt; } +import Base from './base'; +const props = [ + 'xmlFormId', + 'name', + 'version', + 'xml', + 'hash', + 'state', + 'createdBy', + 'createdAt', + 'updatedAt', // Extended metadata - get submissions() { return this._form.submissions; } - get lastSubmission() { return this._form.lastSubmission; } + 'submissions', + 'lastSubmission' +]; + +export default class Form extends Base(props) { + constructor(data) { + super(); + this._data = data; + } nameOrId() { return this.name != null ? this.name : this.xmlFormId; } From 67418898acfd82084479713ca10aa6292d7424c9 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:46 -0400 Subject: [PATCH 02/85] Use form presenter in FormShow --- lib/components/form/show.vue | 5 +++-- lib/presenters/base.js | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/components/form/show.vue b/lib/components/form/show.vue index d644761b0..17001dc29 100644 --- a/lib/components/form/show.vue +++ b/lib/components/form/show.vue @@ -37,6 +37,7 @@ except according to the terms contained in the LICENSE file. diff --git a/lib/router.js b/lib/router.js index 53fe44412..9803aad10 100644 --- a/lib/router.js +++ b/lib/router.js @@ -18,6 +18,7 @@ import AccountResetPassword from './components/account/reset-password.vue'; import BackupList from './components/backup/list.vue'; import FieldKeyList from './components/field-key/list.vue'; import FormList from './components/form/list.vue'; +import FormAttachmentList from './components/form/attachment/list.vue'; import FormOverview from './components/form/overview.vue'; import FormSettings from './components/form/settings.vue'; import FormShow from './components/form/show.vue'; @@ -47,6 +48,7 @@ const routes = [ component: FormShow, children: [ { path: '', component: FormOverview }, + { path: 'media-files', component: FormAttachmentList }, { path: 'submissions', component: FormSubmissions }, { path: 'settings', component: FormSettings } ] From 3617f56ff9418186b3b1a59317f1a40130ad890a Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:50 -0400 Subject: [PATCH 04/85] Fetch form attachments --- lib/components/form/show.vue | 15 +++++++---- test/components/form/analyze.spec.js | 3 ++- test/components/form/delete.spec.js | 14 ++--------- test/components/form/edit.spec.js | 12 +-------- test/components/form/new.spec.js | 1 + test/components/form/overview.spec.js | 24 ++++++++++++------ test/components/form/settings.spec.js | 1 + test/components/form/submissions.spec.js | 2 ++ test/data.js | 26 ++++++++++--------- test/data/form-attachments.js | 32 ++++++++++++++++++++++++ 10 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 test/data/form-attachments.js diff --git a/lib/components/form/show.vue b/lib/components/form/show.vue index 17001dc29..3adabc379 100644 --- a/lib/components/form/show.vue +++ b/lib/components/form/show.vue @@ -47,7 +47,8 @@ export default { data() { return { requestId: null, - form: null + form: null, + attachments: null }; }, computed: { @@ -66,11 +67,15 @@ export default { methods: { fetchData() { this.form = null; + this.attachments = null; const headers = { 'X-Extended-Metadata': 'true' }; - this - .get(`/forms/${this.xmlFormId}`, { headers }) - .then(({ data }) => { - this.form = new Form(data); + this.requestAll([ + this.$http.get(`/forms/${this.xmlFormId}`, { headers }), + this.$http.get(`/forms/${this.xmlFormId}/attachments`, { headers }) + ]) + .then(([form, attachments]) => { + this.form = new Form(form.data); + this.attachments = attachments.data; }) .catch(() => {}); }, diff --git a/test/components/form/analyze.spec.js b/test/components/form/analyze.spec.js index 3c0c2cf9c..0bddba707 100644 --- a/test/components/form/analyze.spec.js +++ b/test/components/form/analyze.spec.js @@ -45,9 +45,10 @@ describe('FormAnalyze', () => { it('selects the OData URL upon click', () => mockRoute(submissionsPath(createFormWithSubmission()), { attachToDocument: true }) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.extendedForms.last()._schema) .respondWithData(testData.submissionOData) - .afterResponse(clickAnalyzeButton) + .afterResponses(clickAnalyzeButton) .then(app => trigger.click(app.first('#form-analyze-odata-url')).then(() => app)) .then(() => { diff --git a/test/components/form/delete.spec.js b/test/components/form/delete.spec.js index 351f0ba17..224f414de 100644 --- a/test/components/form/delete.spec.js +++ b/test/components/form/delete.spec.js @@ -1,14 +1,3 @@ -/* -Copyright 2017 ODK Central Developers -See the NOTICE file at the top-level directory of this distribution and at -https://github.com/opendatakit/central-frontend/blob/master/NOTICE. - -This file is part of ODK Central. It is subject to the license terms in -the LICENSE file found in the top-level directory of this distribution and at -https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, -including this file, may be copied, modified, propagated, or distributed -except according to the terms contained in the LICENSE file. -*/ import FormDelete from '../../../lib/components/form/delete.vue'; import FormSettings from '../../../lib/components/form/settings.vue'; import testData from '../../data'; @@ -50,7 +39,8 @@ describe('FormDelete', () => { const { xmlFormId } = testData.extendedForms.first(); return mockRoute(`/forms/${xmlFormId}/settings`) .respondWithData(() => testData.extendedForms.first()) - .afterResponse(component => { + .respondWithData(() => testData.extendedFormAttachments.sorted()) + .afterResponses(component => { app = component; return clickDeleteButton(app); }) diff --git a/test/components/form/edit.spec.js b/test/components/form/edit.spec.js index 260045797..462ccaeb9 100644 --- a/test/components/form/edit.spec.js +++ b/test/components/form/edit.spec.js @@ -1,14 +1,3 @@ -/* -Copyright 2017 ODK Central Developers -See the NOTICE file at the top-level directory of this distribution and at -https://github.com/opendatakit/central-frontend/blob/master/NOTICE. - -This file is part of ODK Central. It is subject to the license terms in -the LICENSE file found in the top-level directory of this distribution and at -https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, -including this file, may be copied, modified, propagated, or distributed -except according to the terms contained in the LICENSE file. -*/ import FormEdit from '../../../lib/components/form/edit.vue'; import Spinner from '../../../lib/components/spinner.vue'; import faker from '../../faker'; @@ -66,6 +55,7 @@ describe('FormEdit', () => { it('shows a success message', () => mockRoute(settingsPath(testData.extendedForms.createPast(1).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .complete() .request(app => selectDifferentState(app.first(FormEdit))) .respondWithSuccess() diff --git a/test/components/form/new.spec.js b/test/components/form/new.spec.js index 6b433698d..d4ffb4a5d 100644 --- a/test/components/form/new.spec.js +++ b/test/components/form/new.spec.js @@ -139,6 +139,7 @@ describe('FormNew', () => { .then(clickCreateButtonInModal)) .respondWithData(() => testData.simpleForms.last()) // FormNew .respondWithData(() => testData.extendedForms.last()) // FormShow + .respondWithData(() => testData.extendedFormAttachments.sorted()) // FormShow .respondWithData(() => testData.simpleFieldKeys.sorted())); // FormOverview it('redirects to the form overview', () => { diff --git a/test/components/form/overview.spec.js b/test/components/form/overview.spec.js index bc574c031..79a480428 100644 --- a/test/components/form/overview.spec.js +++ b/test/components/form/overview.spec.js @@ -17,8 +17,9 @@ describe('FormOverview', () => { const path = overviewPath(testData.extendedForms.createPast(1).last()); return mockRouteThroughLogin(path) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(app => app.vm.$route.path.should.equal(path)); + .afterResponses(app => app.vm.$route.path.should.equal(path)); }); }); @@ -34,8 +35,9 @@ describe('FormOverview', () => { // . beforeEach(() => mockRoute(overviewPath(testData.extendedForms.last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(component => { + .afterResponses(component => { app = component; })); @@ -65,8 +67,9 @@ describe('FormOverview', () => { }); beforeEach(() => mockRoute(overviewPath(testData.extendedForms.last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(component => { + .afterResponses(component => { app = component; })); @@ -101,8 +104,9 @@ describe('FormOverview', () => { it('no app users', () => mockRoute(overviewPath(testData.extendedForms.createPast(1).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(app => { + .afterResponses(app => { const p = app.find('.form-overview-step')[1].find('p')[1]; p.text().trim().should.containEql('You do not have any App Users on this server yet'); })); @@ -110,11 +114,12 @@ describe('FormOverview', () => { it('at least one app user', () => mockRoute(overviewPath(testData.extendedForms.createPast(1).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => { const count = faker.random.number({ min: 1, max: 2000 }); return testData.simpleFieldKeys.createPast(count).sorted(); }) - .afterResponse(app => { + .afterResponses(app => { const p = app.find('.form-overview-step')[1].find('p')[1]; const count = testData.simpleFieldKeys.size; p.text().trim().should.containEql(` ${count.toLocaleString()} `); @@ -125,8 +130,9 @@ describe('FormOverview', () => { it('is not the current step, if the form is open', () => mockRoute(overviewPath(testData.extendedForms.createPast(1, { isOpen: true }).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(app => { + .afterResponses(app => { const title = app.find('.form-overview-step')[3].first('p'); title.hasClass('text-muted').should.be.true(); })); @@ -134,8 +140,9 @@ describe('FormOverview', () => { it('is marked as complete, if the form is not open', () => mockRoute(overviewPath(testData.extendedForms.createPast(1, { isOpen: false }).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(app => { + .afterResponses(app => { const title = app.find('.form-overview-step')[3].first('p'); title.hasClass('text-success').should.be.true(); })); @@ -143,8 +150,9 @@ describe('FormOverview', () => { it('is marked as complete if the state is changed from open', () => mockRoute(overviewPath(testData.extendedForms.createPast(1, { isOpen: true }).last())) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => testData.simpleFieldKeys.sorted()) - .afterResponse(app => { + .afterResponses(app => { const title = app.find('.form-overview-step')[3].first('p'); title.hasClass('text-success').should.be.false(); }) diff --git a/test/components/form/settings.spec.js b/test/components/form/settings.spec.js index 1ff3fe6a1..705ed0c7c 100644 --- a/test/components/form/settings.spec.js +++ b/test/components/form/settings.spec.js @@ -15,6 +15,7 @@ describe('FormSettings', () => { const path = settingsPath(testData.extendedForms.createPast(1).last()); return mockRouteThroughLogin(path) .respondWithData(() => testData.extendedForms.last()) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .afterResponse(app => app.vm.$route.path.should.equal(path)); }); }); diff --git a/test/components/form/submissions.spec.js b/test/components/form/submissions.spec.js index b86f4c55a..b240b6b44 100644 --- a/test/components/form/submissions.spec.js +++ b/test/components/form/submissions.spec.js @@ -22,6 +22,7 @@ describe('FormSubmissions', () => { const path = submissionsPath(form); return mockRouteThroughLogin(path) .respondWithData(() => form) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .respondWithData(() => form._schema) .respondWithData(testData.submissionOData) .afterResponses(app => app.vm.$route.path.should.equal(path)); @@ -321,6 +322,7 @@ describe('FormSubmissions', () => { it(`refreshes part ${i} of table after refresh button is clicked`, () => mockRoute(submissionsPath(form())) .respondWithData(form) + .respondWithData(() => testData.extendedFormAttachments.sorted()) .testRefreshButton({ collection: testData.extendedSubmissions, respondWithData: [ diff --git a/test/data.js b/test/data.js index 276d64717..6d92b71aa 100644 --- a/test/data.js +++ b/test/data.js @@ -1,19 +1,21 @@ -import * as administrators from './data/administrators'; -import * as backups from './data/backups'; -import * as fieldKeys from './data/fieldKeys'; -import * as forms from './data/forms'; -import * as sessions from './data/sessions'; -import * as submissions from './data/submissions'; +import * as Administrators from './data/administrators'; +import * as Backups from './data/backups'; +import * as FieldKeys from './data/fieldKeys'; +import * as FormAttachments from './data/form-attachments'; +import * as Forms from './data/forms'; +import * as Sessions from './data/sessions'; +import * as Submissions from './data/submissions'; import { resetDataStores } from './data/data-store'; const testData = Object.assign( {}, - administrators, - backups, - fieldKeys, - forms, - sessions, - submissions + Administrators, + Backups, + FieldKeys, + FormAttachments, + Forms, + Sessions, + Submissions ); testData.reset = resetDataStores; diff --git a/test/data/form-attachments.js b/test/data/form-attachments.js new file mode 100644 index 000000000..2f7be9ea9 --- /dev/null +++ b/test/data/form-attachments.js @@ -0,0 +1,32 @@ +import faker from '../faker'; +import { dataStore } from './data-store'; +import { extendedForms } from './forms'; + +const FORM_ATTACHMENT_TYPES = [ + { type: 'image', fileExtension: 'jpg' }, + { type: 'audio', fileExtension: 'mp3' }, + { type: 'video', fileExtension: 'mp4' } +]; + +// eslint-disable-next-line import/prefer-default-export +export const extendedFormAttachments = dataStore({ + factory: ({ + inPast, + lastCreatedAt, + exists = faker.random.boolean() + }) => { + const form = extendedForms.randomOrCreatePast(); + const type = faker.random.arrayElement(FORM_ATTACHMENT_TYPES); + const { updatedAt } = faker.date.timestamps(inPast, [ + lastCreatedAt, + form.createdAt + ]); + return { + type, + name: faker.system.commonFileName(type.fileExtension), + exists, + updatedAt + }; + }, + sort: 'name' +}); From de68a272c750fe91e72f74bdcb6ebfbb5424f307 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:51 -0400 Subject: [PATCH 05/85] Add media files tab --- lib/components/form/attachment/list.vue | 12 +++++++++++- lib/components/form/overview.vue | 4 ++++ lib/components/form/settings.vue | 2 ++ lib/components/form/show.vue | 15 ++++++++++++++- lib/components/form/submissions.vue | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/components/form/attachment/list.vue b/lib/components/form/attachment/list.vue index 4db374ff3..e136c79ad 100644 --- a/lib/components/form/attachment/list.vue +++ b/lib/components/form/attachment/list.vue @@ -15,6 +15,16 @@ except according to the terms contained in the LICENSE file. diff --git a/lib/components/form/overview.vue b/lib/components/form/overview.vue index 7e92aa7a8..759fda74a 100644 --- a/lib/components/form/overview.vue +++ b/lib/components/form/overview.vue @@ -107,6 +107,10 @@ export default { form: { type: Object, required: true + }, + attachments: { + type: Array, + required: true } }, data() { diff --git a/lib/components/form/settings.vue b/lib/components/form/settings.vue index 93dd0ffeb..51e4ba05b 100644 --- a/lib/components/form/settings.vue +++ b/lib/components/form/settings.vue @@ -45,6 +45,8 @@ export default { name: 'FormSettings', components: { FormEdit, FormDelete }, mixins: [modal('deleteForm')], + // Setting this in order to ignore the `attachments` attribute. + inheritAttrs: false, props: { form: { type: Object, diff --git a/lib/components/form/show.vue b/lib/components/form/show.vue index 3adabc379..935a8b948 100644 --- a/lib/components/form/show.vue +++ b/lib/components/form/show.vue @@ -20,6 +20,15 @@ except according to the terms contained in the LICENSE file.
  • Overview
  • +
  • + + Media Files + + {{ missingAttachments.toLocaleString() }} + + +
  • Submissions
  • @@ -30,7 +39,8 @@ except according to the terms contained in the LICENSE file. - + @@ -54,6 +64,9 @@ export default { computed: { xmlFormId() { return this.$route.params.xmlFormId; + }, + missingAttachments() { + return this.attachments.filter(attachment => !attachment.exists).length; } }, watch: { diff --git a/lib/components/form/submissions.vue b/lib/components/form/submissions.vue index 7d1b2c5a3..0604ab8d1 100644 --- a/lib/components/form/submissions.vue +++ b/lib/components/form/submissions.vue @@ -96,6 +96,8 @@ export default { modal('analyze'), request() ], + // Setting this in order to ignore the `attachments` attribute. + inheritAttrs: false, props: { form: { type: Object, From afa0bd205c93e0958bf43de5425b71e67a9bd69c Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:53 -0400 Subject: [PATCH 06/85] Use $pluralize() in FormOverview $pluralize() was not in place when we implemented FormOverview. Adding it now simplifies the component. --- lib/components/form/overview.vue | 27 ++++++++------------------- test/components/form/overview.spec.js | 4 ++-- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/components/form/overview.vue b/lib/components/form/overview.vue index 759fda74a..043beb728 100644 --- a/lib/components/form/overview.vue +++ b/lib/components/form/overview.vue @@ -38,8 +38,10 @@ except according to the terms contained in the LICENSE file. Nobody has submitted any data to this form yet. App Users will be able to see this form on their mobile device to download and fill out. @@ -52,7 +54,7 @@ except according to the terms contained in the LICENSE file. @@ -70,7 +72,9 @@ except according to the terms contained in the LICENSE file. to monitor and analyze the data for quality and results. You can do this with the Download and Analyze buttons on the @@ -119,21 +123,6 @@ export default { fieldKeyCount: null }; }, - computed: { - submissionCountString() { - const count = this.form.submissions.toLocaleString(); - const s = this.form.submissions !== 1 ? 's' : ''; - return `${count} submission${s}`; - }, - have() { - return this.form.submissions === 1 ? 'has' : 'have'; - }, - fieldKeyCountString() { - const count = this.fieldKeyCount.toLocaleString(); - const s = this.fieldKeyCount !== 1 ? 's' : ''; - return `${count} App User${s}`; - } - }, created() { this.fetchData(); }, diff --git a/test/components/form/overview.spec.js b/test/components/form/overview.spec.js index 79a480428..007b4b96a 100644 --- a/test/components/form/overview.spec.js +++ b/test/components/form/overview.spec.js @@ -82,7 +82,7 @@ describe('FormOverview', () => { it('shows the number of submissions', () => { const p = app.find('.form-overview-step')[1].find('p')[1]; const count = testData.extendedForms.last().submissions; - p.text().trim().should.containEql(` ${count.toLocaleString()} `); + p.text().trim().should.containEql(count.toLocaleString()); }); }); @@ -95,7 +95,7 @@ describe('FormOverview', () => { it('shows the number of submissions', () => { const p = app.find('.form-overview-step')[2].find('p')[1]; const count = testData.extendedForms.last().submissions; - p.text().trim().should.containEql(` ${count.toLocaleString()} `); + p.text().trim().should.containEql(count.toLocaleString()); }); }); }); From 6f5cc6d8fe84fd64e6879cfbb3b00359320ceb11 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:55 -0400 Subject: [PATCH 07/85] Add FormOverviewStep component This should help clarify the logic around the step stage and how that affects the heading and icon text classes. --- lib/components/form/overview-step.vue | 73 ++++++++++++++++++++++ lib/components/form/overview.vue | 89 ++++++++++----------------- 2 files changed, 105 insertions(+), 57 deletions(-) create mode 100644 lib/components/form/overview-step.vue diff --git a/lib/components/form/overview-step.vue b/lib/components/form/overview-step.vue new file mode 100644 index 000000000..582fdb017 --- /dev/null +++ b/lib/components/form/overview-step.vue @@ -0,0 +1,73 @@ + + + + + + diff --git a/lib/components/form/overview.vue b/lib/components/form/overview.vue index 043beb728..b62b8e642 100644 --- a/lib/components/form/overview.vue +++ b/lib/components/form/overview.vue @@ -15,24 +15,17 @@ except according to the terms contained in the LICENSE file.

    Checklist

    -
    -

    - Create and upload form -

    + +

    Great work! Your form design has been loaded successfully. It is ready to accept submissions. You will have to start over with a new form if you wish to make changes to the form questions.

    -
    -
    -
    -

    - - Download form on survey clients and submit data -

    + + +

    -
    -
    -
    -

    - Evaluate and - analyze submitted data -

    + + +

    - - From 41f43b559df5164feaa801b3a809d857a7a03563 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Thu, 9 Aug 2018 15:49:56 -0400 Subject: [PATCH 08/85] Add step to form overview for form attachments --- lib/components/form/overview.vue | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/components/form/overview.vue b/lib/components/form/overview.vue index b62b8e642..021b99e16 100644 --- a/lib/components/form/overview.vue +++ b/lib/components/form/overview.vue @@ -24,7 +24,17 @@ except according to the terms contained in the LICENSE file. questions.

    - + + +

    + Your form design references files that we need in order to present + your form. You can upload these for distribution under the + Media Files + tab. If you change your mind or make a replace, you can always + replace the files. +

    +
    +

    - +

    diff --git a/lib/components/form/attachment/popup.vue b/lib/components/form/attachment/popup.vue new file mode 100644 index 000000000..b12069b25 --- /dev/null +++ b/lib/components/form/attachment/popup.vue @@ -0,0 +1,97 @@ + + + + + + diff --git a/lib/components/form/attachment/row.vue b/lib/components/form/attachment/row.vue index 26e9cafc8..24bd485f9 100644 --- a/lib/components/form/attachment/row.vue +++ b/lib/components/form/attachment/row.vue @@ -10,14 +10,19 @@ including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. --> @@ -91,9 +92,7 @@ export default { // Only applicable for countOfFilesOverDropZone === 1. dragoverAttachment: null, // Array of file/attachment pairs - // TODO: Rename so that there is no implication that it is an array of - // files? `uploadsToEnqueue`? - filesToUpload: [], + plannedUploads: [], // TODO: Only store the length? unmatchedFiles: [], uploadStatus: { @@ -156,13 +155,13 @@ export default { }, // files is a FileList, not an Array. matchFilesToAttachments(files) { - this.filesToUpload = []; + this.plannedUploads = []; this.unmatchedFiles = []; for (let i = 0; i < files.length; i += 1) { const file = files[i]; const attachment = this.attachments.find(a => a.name === file.name); if (attachment != null) - this.filesToUpload.push({ attachment, file }); + this.plannedUploads.push({ attachment, file }); else this.unmatchedFiles.push(file); } @@ -172,10 +171,10 @@ export default { return `/forms/${this.form.xmlFormId}/attachments/${encodedName}`; }, uploadFiles() { - this.uploadStatus.total = this.filesToUpload.length; + this.uploadStatus.total = this.plannedUploads.length; this.uploadStatus.complete = 0; const uploaded = []; - const promise = this.filesToUpload.reduce( + const promise = this.plannedUploads.reduce( (acc, { attachment, file }) => acc .then(() => { this.uploadStatus.current = file.name; @@ -200,7 +199,7 @@ export default { } this.uploadStatus = { total: null, complete: null, current: null }; }); - this.filesToUpload = []; + this.plannedUploads = []; this.unmatchedFiles = []; }, ondrop(jQueryEvent) { @@ -209,9 +208,9 @@ export default { this.matchFilesToAttachments(files); else if (this.dragoverAttachment != null) { const file = files[0]; - this.filesToUpload = [{ file, attachment: this.dragoverAttachment }]; + this.plannedUploads = [{ file, attachment: this.dragoverAttachment }]; this.dragoverAttachment = null; - if (file.name === this.filesToUpload[0].attachment.name) + if (file.name === this.plannedUploads[0].attachment.name) this.uploadFiles(); else this.nameMismatch.state = true; @@ -221,7 +220,7 @@ export default { cancelUpload() { // Checking `length` in order to avoid setting these properties // unnecessarily, which could result in Vue calculations. - if (this.filesToUpload.length !== 0) this.filesToUpload = []; + if (this.plannedUploads.length !== 0) this.plannedUploads = []; if (this.unmatchedFiles.length !== 0) this.unmatchedFiles = []; } } diff --git a/lib/components/form/attachment/name-mismatch.vue b/lib/components/form/attachment/name-mismatch.vue index 9e4b167dc..f0a9daead 100644 --- a/lib/components/form/attachment/name-mismatch.vue +++ b/lib/components/form/attachment/name-mismatch.vue @@ -47,22 +47,22 @@ export default { type: Boolean, default: false }, - filesToUpload: { + plannedUploads: { type: Array, required: true } }, computed: { // Returning dummy values for `attachment` and `file` when - // filesToUpload.length !== 1 in order to simplify the template. + // plannedUploads.length !== 1 in order to simplify the template. attachment() { - return this.filesToUpload.length === 1 - ? this.filesToUpload[0].attachment + return this.plannedUploads.length === 1 + ? this.plannedUploads[0].attachment : { name: '', exists: false }; }, file() { - return this.filesToUpload.length === 1 - ? this.filesToUpload[0].file + return this.plannedUploads.length === 1 + ? this.plannedUploads[0].file : { name: '' }; } }, diff --git a/lib/components/form/attachment/popups.vue b/lib/components/form/attachment/popups.vue index c60c9bcf4..c8a424204 100644 --- a/lib/components/form/attachment/popups.vue +++ b/lib/components/form/attachment/popups.vue @@ -11,7 +11,7 @@ except according to the terms contained in the LICENSE file. --> -