diff --git a/ghost/admin/app/controllers/settings/labs.js b/ghost/admin/app/controllers/settings/labs.js
index 3dcb4bcd26..956882d563 100644
--- a/ghost/admin/app/controllers/settings/labs.js
+++ b/ghost/admin/app/controllers/settings/labs.js
@@ -25,6 +25,14 @@ const IMPORT_MIME_TYPES = [
const JSON_EXTENSION = ['json'];
const JSON_MIME_TYPE = ['application/json'];
+const YAML_EXTENSION = ['yml', 'yaml'];
+const YAML_MIME_TYPE = [
+ 'text/vnd.yaml',
+ 'application/vnd.yaml',
+ 'text/x-yaml',
+ 'application/x-yaml'
+];
+
export default Controller.extend({
ajax: service(),
config: service(),
@@ -43,12 +51,16 @@ export default Controller.extend({
importMimeType: null,
jsonExtension: null,
jsonMimeType: null,
+ yamlExtension: null,
+ yamlMimeType: null,
init() {
this._super(...arguments);
this.importMimeType = IMPORT_MIME_TYPES;
this.jsonExtension = JSON_EXTENSION;
this.jsonMimeType = JSON_MIME_TYPE;
+ this.yamlExtension = YAML_EXTENSION;
+ this.yamlMimeType = YAML_MIME_TYPE;
},
actions: {
@@ -214,6 +226,17 @@ export default Controller.extend({
return true;
}).drop(),
+ routesUploadResult: task(function* (success) {
+ this.set('routesSuccess', success);
+ this.set('routesFailure', !success);
+
+ yield timeout(config.environment === 'test' ? 100 : 5000);
+
+ this.set('routesSuccess', null);
+ this.set('routesFailure', null);
+ return true;
+ }).drop(),
+
reset() {
this.set('importErrors', null);
this.set('importSuccessful', false);
diff --git a/ghost/admin/app/templates/settings/labs.hbs b/ghost/admin/app/templates/settings/labs.hbs
index 41482e891a..b9a83f4ef4 100644
--- a/ghost/admin/app/templates/settings/labs.hbs
+++ b/ghost/admin/app/templates/settings/labs.hbs
@@ -163,7 +163,51 @@
{{/gh-uploader}}
+
+ {{#gh-uploader
+ extensions=yamlExtension
+ uploadUrl="/settings/routes/yaml/"
+ paramName="routes"
+ onUploadSuccess=(perform routesUploadResult true)
+ onUploadFailure=(perform routesUploadResult false)
+ as |uploader|
+ }}
+
+
Routes
+
Configure dynamic routing by modifying the routes.yaml file
+ {{#each uploader.errors as |error|}}
+
{{error.message}}
+ {{/each}}
+
+
+ {{#if uploader.isUploading}}
+ {{uploader.progressBar}}
+ {{else}}
+
+
Download current routes.yml
+ {{/if}}
+
+ {{gh-file-input multiple=false action=uploader.setFiles accept=yamlMimeType data-test-file-input="routes"}}
+
+
+ {{/gh-uploader}}
+
diff --git a/ghost/admin/tests/acceptance/settings/labs-test.js b/ghost/admin/tests/acceptance/settings/labs-test.js
index 3fa6a4e2bb..ac47a673fe 100644
--- a/ghost/admin/tests/acceptance/settings/labs-test.js
+++ b/ghost/admin/tests/acceptance/settings/labs-test.js
@@ -195,5 +195,118 @@ describe('Acceptance: Settings - Labs', function () {
let iframe = $('#iframeDownload');
expect(iframe.attr('src')).to.have.string('/redirects/json/');
});
+
+ it('can upload/download routes.yaml', async function () {
+ await visit('/settings/labs');
+
+ // successful upload
+ server.post('/settings/routes/yaml/', {}, 200);
+
+ await fileUpload(
+ '[data-test-file-input="routes"]',
+ ['test'],
+ {name: 'routes.yaml', type: 'application/x-yaml'}
+ );
+
+ // TODO: tests for the temporary success/failure state have been
+ // disabled because they were randomly failing
+
+ // this should be half-way through button reset timeout
+ // await timeout(50);
+ //
+ // // shows success button
+ // let button = find('[data-test-button="upload-routes"]');
+ // expect(button.length, 'no of success buttons').to.equal(1);
+ // expect(
+ // button.hasClass('gh-btn-green'),
+ // 'success button is green'
+ // ).to.be.true;
+ // expect(
+ // button.text().trim(),
+ // 'success button text'
+ // ).to.have.string('Uploaded');
+ //
+ // await wait();
+
+ // returned to normal button
+ let button = find('[data-test-button="upload-routes"]');
+ expect(button.length, 'no of post-success buttons').to.equal(1);
+ expect(
+ button.hasClass('gh-btn-green'),
+ 'routes post-success button doesn\'t have success class'
+ ).to.be.false;
+ expect(
+ button.text().trim(),
+ 'routes post-success button text'
+ ).to.have.string('Upload routes YAML');
+
+ // failed upload
+ server.post('/settings/routes/yaml/', {
+ errors: [{
+ errorType: 'BadRequestError',
+ message: 'Test failure message'
+ }]
+ }, 400);
+
+ await fileUpload(
+ '[data-test-file-input="routes"]',
+ ['test'],
+ {name: 'routes-bad.yaml', type: 'application/x-yaml'}
+ );
+
+ // TODO: tests for the temporary success/failure state have been
+ // disabled because they were randomly failing
+
+ // this should be half-way through button reset timeout
+ // await timeout(50);
+ //
+ // shows failure button
+ // button = find('[data-test-button="upload-routes"]');
+ // expect(button.length, 'no of failure buttons').to.equal(1);
+ // expect(
+ // button.hasClass('gh-btn-red'),
+ // 'failure button is red'
+ // ).to.be.true;
+ // expect(
+ // button.text().trim(),
+ // 'failure button text'
+ // ).to.have.string('Upload Failed');
+ //
+ // await wait();
+
+ // shows error message
+ expect(
+ find('[data-test-error="routes"]').text().trim(),
+ 'routes upload error text'
+ ).to.have.string('Test failure message');
+
+ // returned to normal button
+ button = find('[data-test-button="upload-routes"]');
+ expect(button.length, 'no of post-failure buttons').to.equal(1);
+ expect(
+ button.hasClass('gh-btn-red'),
+ 'routes post-failure button doesn\'t have failure class'
+ ).to.be.false;
+ expect(
+ button.text().trim(),
+ 'routes post-failure button text'
+ ).to.have.string('Upload routes YAML');
+
+ // successful upload clears error
+ server.post('/settings/routes/yaml/', {}, 200);
+ await fileUpload(
+ '[data-test-file-input="routes"]',
+ ['test'],
+ {name: 'routes-good.yaml', type: 'application/x-yaml'}
+ );
+
+ expect(find('[data-test-error="routes"]')).to.not.exist;
+
+ // can download redirects.json
+ await click('[data-test-link="download-routes"]');
+
+ let iframe = $('#iframeDownload');
+ expect(iframe.attr('src')).to.have.string('/settings/routes/yaml/');
+ });
});
});