Added upload progress stage to members importer

no issue

- As the number of states for the component has become larger introduced a concept of "stages" on component level to easier track UI changes.
This commit is contained in:
Nazar Gargol 2020-07-09 17:31:28 +12:00
parent dc82014283
commit 3086ac1439
2 changed files with 133 additions and 107 deletions

View File

@ -1,7 +1,11 @@
<header class="modal-header" data-test-modal="import-members"> <header class="modal-header" data-test-modal="import-members">
<h1> <h1>
{{#if this.importResponse}} {{#if this.summary}}
Import complete{{unless this.importResponse.invalid.count "!"}} Import complete{{unless this.importResponse.invalid.count "!"}}
{{/if}}
{{#if this.uploading}}
Importing members
{{else}} {{else}}
Import members Import members
{{/if}} {{/if}}
@ -9,127 +13,131 @@
</header> </header>
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a> <a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
{{#if this.importResponse}} <div class="modal-body">
{{!-- post upload step with import stats --}} {{#if (and this.filePresent (not this.failureMessage))}}
{{#if this.validating}}
<div class="modal-body"> {{#if validationErrors}}
<div class="failed flex items-start gh-members-upload-errorcontainer {{if this.importDisabled "error" "warning"}}">
{{!-- Summary --}} <div class="mr3">
<div class="ba b--whitegrey br3 middarkgrey bg-whitegrey-l2"> {{#if this.importDisabled}}
<div class="flex items-start"> {{svg-jar "warning" class="nudge-top--2 w5 h5 fill-red"}}
<div class="w-50 gh-member-import-result-summary"> {{else}}
<h2>{{format-number this.importResponse.imported.count}}</h2> {{svg-jar "warning" class="nudge-top--2 w5 h5 fill-yellow-d1"}}
<p>{{pluralize this.importResponse.imported.count "Member" without-count=true}} added</p> {{/if}}
</div> </div>
<div class="bl b--whitegrey w-50 gh-member-import-result-summary"> <div class="ma0">
<h2>{{format-number this.importResponse.invalid.count}}</h2> <p class="ma0 pa0 flex-grow w-100">The CSV contains errors! {{unless this.importDisabled "Some members will not be imported."}}</p>
<p>{{pluralize this.importResponse.invalid.count "Error" without-count=true}}</p> {{#if validationErrors}}
</div>
</div>
</div>
{{#if this.importResponse.invalid.count}}
<div class="mt6 gh-members-upload-errorcontainer error">
<div class="flex items-start">
{{svg-jar "warning" class="w5 h5 fill-red nudge-top--3 mr3"}}
<div class="flex-grow w-100">
<p class="ma0 pa0">{{format-number this.importResponse.invalid.count}} {{pluralize this.importResponse.invalid.count "member" without-count=true}} were skipped due to the following errors:</p>
{{#if this.config.enableDeveloperExperiments}}
<ul class="ma0 pa0 mt4 list bt b--whitegrey"> <ul class="ma0 pa0 mt4 list bt b--whitegrey">
{{#each this.importResponse.invalid.errors as |error|}} {{#each validationErrors as |error|}}
<li class="gh-members-import-errormessage">{{error.message}} <span class="fw6">({{format-number error.count}})</span></li> <li class="gh-members-import-errormessage">
<span>{{{error.message}}}</span>
{{#if error.context}}
<p class="gh-members-import-errorcontext">{{{error.context}}}</p>
{{/if}}
</li>
{{/each}} {{/each}}
</ul> </ul>
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{else}}
<div class="bg-whitegrey-l2 ba b--whitegrey br3 gh-image-uploader gh-members-import-spinner">
<div class="gh-loading-content">
<div class="gh-loading-spinner"></div>
</div>
<div class="description midgrey">Validating...</div>
</div>
{{/if}}
{{/if}}
{{#if this.customizing}}
<GhFormGroup>
{{#if this.config.enableDeveloperExperiments}}
<h4 class="fw6 f8 dib mb1">Mapping</h4>
<div class="gh-members-import-scrollarea">
<GhMembersImportTable
@importData={{this.fileData}}
@mapping={{this.mapping}}
@updateMapping={{action "updateMapping"}}/>
</div>
<p>Match the fields in your uploaded file to Ghost members.</p>
{{else}}
<div class="bg-whitegrey-l2 ba b--whitegrey br3">
<div class="flex flex-column items-center justify-center gh-members-import-file">
{{svg-jar "file-tabular-data" class="w9 h9 mb1 stroke-midgrey"}}
<div class="description midgrey">{{this.file.name}}</div>
</div>
</div>
{{/if}}
<div class="mt4">
<label for="label-input"><span class="fw6 f8 dib mb1">Labels</span></label>
<GhMemberLabelInput @member={{this.labels}} @triggerId="label-input" />
<p>Will be applied to all newly imported members</p>
</div>
</GhFormGroup>
{{/if}}
{{#if this.uploading}}
<div class="progress-container">
<div class="gh-progress-container-progress">
<div class="gh-progress-bar {{if this.failureMessage "-error"}}" style={{this.progressStyle}}></div>
</div>
</div> </div>
{{/if}} {{/if}}
</div>
{{else}} {{#if this.summary}}
<div class="modal-body"> <div class="ba b--whitegrey br3 middarkgrey bg-whitegrey-l2">
{{#if (and this.filePresent (not this.failureMessage))}} <div class="flex items-start">
{{#if this.validating}} <div class="w-50 gh-member-import-result-summary">
{{#if validationErrors}} <h2>{{format-number this.importResponse.imported.count}}</h2>
<div class="failed flex items-start gh-members-upload-errorcontainer {{if this.importDisabled "error" "warning"}}"> <p>{{pluralize this.importResponse.imported.count "Member" without-count=true}} added</p>
<div class="mr3"> </div>
{{#if this.importDisabled}} <div class="bl b--whitegrey w-50 gh-member-import-result-summary">
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-red"}} <h2>{{format-number this.importResponse.invalid.count}}</h2>
{{else}} <p>{{pluralize this.importResponse.invalid.count "Error" without-count=true}}</p>
{{svg-jar "warning" class="nudge-top--2 w5 h5 fill-yellow-d1"}} </div>
{{/if}} </div>
</div> </div>
<div class="ma0">
<p class="ma0 pa0 flex-grow w-100">The CSV contains errors! {{unless this.importDisabled "Some members will not be imported."}}</p> {{#if this.importResponse.invalid.count}}
{{#if validationErrors}} <div class="mt6 gh-members-upload-errorcontainer error">
<div class="flex items-start">
{{svg-jar "warning" class="w5 h5 fill-red nudge-top--3 mr3"}}
<div class="flex-grow w-100">
<p class="ma0 pa0">{{format-number this.importResponse.invalid.count}} {{pluralize this.importResponse.invalid.count "member" without-count=true}} were skipped due to the following errors:</p>
{{#if this.config.enableDeveloperExperiments}}
<ul class="ma0 pa0 mt4 list bt b--whitegrey"> <ul class="ma0 pa0 mt4 list bt b--whitegrey">
{{#each validationErrors as |error|}} {{#each this.importResponse.invalid.errors as |error|}}
<li class="gh-members-import-errormessage"> <li class="gh-members-import-errormessage">{{error.message}} <span class="fw6">({{format-number error.count}})</span></li>
<span>{{{error.message}}}</span>
{{#if error.context}}
<p class="gh-members-import-errorcontext">{{{error.context}}}</p>
{{/if}}
</li>
{{/each}} {{/each}}
</ul> </ul>
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{else}}
<div class="bg-whitegrey-l2 ba b--whitegrey br3 gh-image-uploader gh-members-import-spinner">
<div class="gh-loading-content">
<div class="gh-loading-spinner"></div>
</div>
<div class="description midgrey">Validating...</div>
</div>
{{/if}}
{{else}}
<GhFormGroup>
{{#if this.config.enableDeveloperExperiments}}
<h4 class="fw6 f8 dib mb1">Mapping</h4>
<div class="gh-members-import-scrollarea">
<GhMembersImportTable
@importData={{this.fileData}}
@mapping={{this.mapping}}
@updateMapping={{action "updateMapping"}}/>
</div>
<p>Match the fields in your uploaded file to Ghost members.</p>
{{else}}
<div class="bg-whitegrey-l2 ba b--whitegrey br3">
<div class="flex flex-column items-center justify-center gh-members-import-file">
{{svg-jar "file-tabular-data" class="w9 h9 mb1 stroke-midgrey"}}
<div class="description midgrey">{{this.file.name}}</div>
</div>
</div>
{{/if}}
<div class="mt4">
<label for="label-input"><span class="fw6 f8 dib mb1">Labels</span></label>
<GhMemberLabelInput @member={{this.labels}} @triggerId="label-input" />
<p>Will be applied to all newly imported members</p>
</div>
</GhFormGroup>
{{/if}}
{{else}}
{{#if this.failureMessage}}
<div class="failed flex items-start gh-members-upload-errorcontainer error">
<div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
<p class="ma0 pa0">{{this.failureMessage}}</p>
</div> </div>
{{/if}} {{/if}}
<div class="upload-form bg-whitegrey-l2 ba b--whitegrey br3"> {{/if}}
<section class="gh-image-uploader gh-members-import-uploader {{this.dragClass}}"> {{else}}
<GhFileInput @multiple={{false}} @alt={{this.labelText}} @action={{action "fileSelected"}} @accept={{this.accept}}> {{#if this.failureMessage}}
<div class="flex flex-column items-center"> <div class="failed flex items-start gh-members-upload-errorcontainer error">
{{svg-jar "upload" class="w9 h9 mb1 stroke-midgrey"}} <div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
<div class="description midgrey">{{this.labelText}}</div> <p class="ma0 pa0">{{this.failureMessage}}</p>
</div>
</GhFileInput>
</section>
</div> </div>
{{/if}} {{/if}}
</div> <div class="upload-form bg-whitegrey-l2 ba b--whitegrey br3">
{{/if}} <section class="gh-image-uploader gh-members-import-uploader {{this.dragClass}}">
<GhFileInput @multiple={{false}} @alt={{this.labelText}} @action={{action "fileSelected"}} @accept={{this.accept}}>
<div class="flex flex-column items-center">
{{svg-jar "upload" class="w9 h9 mb1 stroke-midgrey"}}
<div class="description midgrey">{{this.labelText}}</div>
</div>
</GhFileInput>
</section>
</div>
{{/if}}
</div>
<div class="modal-footer {{if (and this.filePresent (not this.failureMessage) (not this.importResponse)) "modal-footer-spread"}}"> <div class="modal-footer {{if (and this.filePresent (not this.failureMessage) (not this.importResponse)) "modal-footer-spread"}}">
{{#if this.importResponse}} {{#if this.importResponse}}
@ -148,7 +156,9 @@
</button> </button>
{{/unless}} {{/unless}}
{{/if}} {{/if}}
{{else}} {{/if}}
{{#if this.customizing}}
<button {{action "reset"}} class="gh-btn" data-test-button="close-import-members"> <button {{action "reset"}} class="gh-btn" data-test-button="close-import-members">
<span>Start over</span> <span>Start over</span>
</button> </button>

View File

@ -71,13 +71,17 @@ export default ModalComponent.extend({
labelText: 'Select or drag-and-drop a CSV file', labelText: 'Select or drag-and-drop a CSV file',
// import stages, default is "CSV file selection"
validating: false,
customizing: false,
uploading: false,
summary: false,
dragClass: null, dragClass: null,
file: null, file: null,
fileData: null, fileData: null,
mapping: null, mapping: null,
paramName: 'membersfile', paramName: 'membersfile',
validating: false,
uploading: false,
uploadPercentage: 0, uploadPercentage: 0,
importResponse: null, importResponse: null,
failureMessage: null, failureMessage: null,
@ -161,6 +165,7 @@ export default ModalComponent.extend({
// TODO: remove "if" below once import validations are production ready // TODO: remove "if" below once import validations are production ready
if (this.config.get('enableDeveloperExperiments')) { if (this.config.get('enableDeveloperExperiments')) {
this.set('validating', true); this.set('validating', true);
papaparse.parse(file, { papaparse.parse(file, {
header: true, header: true,
skipEmptyLines: true, skipEmptyLines: true,
@ -175,12 +180,15 @@ export default ModalComponent.extend({
this._importValidationFailed(validationErrors); this._importValidationFailed(validationErrors);
} else { } else {
this.set('validating', false); this.set('validating', false);
this.set('customizing', true);
} }
}, },
error: (error) => { error: (error) => {
this._validationFailed(error); this._validationFailed(error);
} }
}); });
} else {
this.set('customizing', true);
} }
} }
}, },
@ -192,6 +200,11 @@ export default ModalComponent.extend({
this.set('fileData', null); this.set('fileData', null);
this.set('mapping', null); this.set('mapping', null);
this.set('validationErrors', null); this.set('validationErrors', null);
this.set('validating', false);
this.set('customizing', false);
this.set('uploading', false);
this.set('summary', false);
}, },
upload() { upload() {
@ -202,6 +215,7 @@ export default ModalComponent.extend({
continueImport() { continueImport() {
this.set('validating', false); this.set('validating', false);
this.set('customizing', true);
}, },
confirm() { confirm() {
@ -280,6 +294,7 @@ export default ModalComponent.extend({
}, },
_uploadStarted() { _uploadStarted() {
this.set('customizing', false);
this.set('uploading', true); this.set('uploading', true);
}, },
@ -300,6 +315,7 @@ export default ModalComponent.extend({
_uploadFinished() { _uploadFinished() {
this.set('uploading', false); this.set('uploading', false);
this.set('summary', true);
}, },
_importValidationFailed(errors) { _importValidationFailed(errors) {