From f92c62b59abf745099a17c02be3c93e41d02dabf Mon Sep 17 00:00:00 2001 From: Kevin Ansfield <kevin@lookingsideways.co.uk> Date: Mon, 14 Nov 2022 09:41:09 +0000 Subject: [PATCH] Switched API key regeneration modal to new modal pattern refs https://github.com/TryGhost/Team/issues/1734 refs https://github.com/TryGhost/Team/issues/559 refs https://github.com/TryGhost/Ghost/issues/14101 - switches to newer modal patterns ready for later Ember upgrades - migrated Zapier controller to native class syntax - fixed regeneration confirmation text not being visible on Zapier screen --- ghost/admin/.lint-todo | 6 ++ .../app/components/modal-regenerate-key.hbs | 22 ------ .../app/components/modal-regenerate-key.js | 44 ------------ .../integrations/regenerate-key-modal.hbs | 29 ++++++++ .../integrations/regenerate-key-modal.js | 35 ++++++++++ .../app/controllers/settings/integration.js | 34 +++------- .../settings/integrations/zapier.js | 68 ++++++++----------- .../admin/app/routes/settings/integration.js | 14 ++-- .../routes/settings/integrations/zapier.js | 14 ++-- ghost/admin/app/styles/layouts/apps.css | 4 +- .../app/templates/settings/integration.hbs | 31 +++------ .../settings/integrations/zapier.hbs | 36 ++++------ 12 files changed, 136 insertions(+), 201 deletions(-) delete mode 100644 ghost/admin/app/components/modal-regenerate-key.hbs delete mode 100644 ghost/admin/app/components/modal-regenerate-key.js create mode 100644 ghost/admin/app/components/settings/integrations/regenerate-key-modal.hbs create mode 100644 ghost/admin/app/components/settings/integrations/regenerate-key-modal.js diff --git a/ghost/admin/.lint-todo b/ghost/admin/.lint-todo index 3e598c38b5..933319b080 100644 --- a/ghost/admin/.lint-todo +++ b/ghost/admin/.lint-todo @@ -1010,3 +1010,9 @@ remove|ember-template-lint|no-action|50|35|50|35|7432725bd18c48f69bf22dc9487d14d remove|ember-template-lint|no-passed-in-event-handlers|33|28|33|28|5b371baf419f247953b91b626611cb831c524af3|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs remove|ember-template-lint|no-passed-in-event-handlers|50|28|50|28|40caf07c7cebf6f4321c5b7e7f2f426b5c30217b|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs remove|ember-template-lint|require-iframe-title|1|0|1|0|d1c9631d150af53ca33b16c8c280c9d815bf43da|1662681600000|1673053200000|1678237200000|app/components/gh-billing-iframe.hbs +remove|ember-template-lint|no-action|33|66|33|66|2e441eb8956fdd6684b75ae1139f097237ebc1fa|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs +remove|ember-template-lint|no-action|36|66|36|66|2d51bed033fe50baea3002bf5acc234ac3738b58|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs +remove|ember-template-lint|no-action|58|66|58|66|bfcab210b651dbe74a1b9e5eb565756ab863f3bb|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs +remove|ember-template-lint|no-action|253|17|253|17|1907c9b1ae81cfb450b7a2ae7e0f874c26595144|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs +remove|ember-template-lint|no-action|254|15|254|15|20eb710122c95e74c9293fe14e11f105a86f172a|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs +remove|ember-template-lint|require-valid-alt-text|18|24|18|24|c1c041ff951875c8cf87ba24bd45d2a2a7597969|1662681600000|1673053200000|1678237200000|app/templates/settings/integrations/zapier.hbs diff --git a/ghost/admin/app/components/modal-regenerate-key.hbs b/ghost/admin/app/components/modal-regenerate-key.hbs deleted file mode 100644 index b3533c133b..0000000000 --- a/ghost/admin/app/components/modal-regenerate-key.hbs +++ /dev/null @@ -1,22 +0,0 @@ -<header class="modal-header"> - <h1>Regenerate {{capitalize this.apiKey.type}} API Key</h1> -</header> -<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a> - -<div class="modal-body"> - <p> - {{#if (eq this.internalIntegration "zapier")}} - You will need to locate the Ghost App within your Zapier account and click on "Reconnect" to enter the new Admin API Key. - {{else}} - You can regenerate <strong>{{capitalize this.apiKey.type}} API Key</strong> any time, but any scripts or applications using it will need to be updated. - {{/if}} - </p> - {{#if this.errorMessage}} - <p class='red'> {{this.errorMessage}}</p> - {{/if}} -</div> - -<div class="modal-footer"> - <button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button> - <GhTaskButton @buttonText="Regenerate {{capitalize this.apiKey.type}} API Key" @successText="Regenerated" @task={{this.regenerateKey}} @class="gh-btn gh-btn-icon gh-btn-red" /> -</div> diff --git a/ghost/admin/app/components/modal-regenerate-key.js b/ghost/admin/app/components/modal-regenerate-key.js deleted file mode 100644 index e9b8a4f7cf..0000000000 --- a/ghost/admin/app/components/modal-regenerate-key.js +++ /dev/null @@ -1,44 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import {alias} from '@ember/object/computed'; -import {capitalize} from '@ember/string'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default ModalComponent.extend({ - ajax: service(), - store: service(), - ghostPaths: service(), - - errorMessage: null, - - // Allowed actions - confirm: () => {}, - - apiKey: alias('model.apiKey'), - integration: alias('model.integration'), - internalIntegration: alias('model.internalIntegration'), - - actions: { - confirm() { - this.regenerateApiKey.perform(); - } - }, - - regenerateKey: task(function* () { - let url = this.get('ghostPaths.url').api('/integrations/', this.integration.id, 'api_key', this.apiKey.id, 'refresh'); - try { - const response = yield this.ajax.post(url, { - data: { - integrations: [{id: this.integration.id}] - } - }); - this.store.pushPayload(response); - yield this.confirm(); - this.send('closeModal'); - } catch (e) { - let errMessage = `There was an error regenerating the ${capitalize(this.apiKey.type)} API Key. Please try again`; - this.set('errorMessage', errMessage); - return; - } - }).drop() -}); diff --git a/ghost/admin/app/components/settings/integrations/regenerate-key-modal.hbs b/ghost/admin/app/components/settings/integrations/regenerate-key-modal.hbs new file mode 100644 index 0000000000..7049c53e10 --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/regenerate-key-modal.hbs @@ -0,0 +1,29 @@ +<div class="modal-content" data-test-modal="regenerate-key"> + <header class="modal-header"> + <h1>Regenerate {{capitalize @data.apiKey.type}} API Key</h1> + </header> + <button type="button" class="close" title="Close" {{on "click" (fn @close null)}}>{{svg-jar "close"}}<span class="hidden">Close</span></button> + + <div class="modal-body"> + <p> + {{#if (eq @data.internalIntegration "zapier")}} + You will need to locate the Ghost App within your Zapier account and click on "Reconnect" to enter the new Admin API Key. + {{else}} + You can regenerate <strong>{{capitalize @data.apiKey.type}} API Key</strong> any time, but any scripts or applications using it will need to be updated. + {{/if}} + </p> + {{#if this.errorMessage}} + <p class='red'> {{this.errorMessage}}</p> + {{/if}} + </div> + + <div class="modal-footer"> + <button class="gh-btn" type="button" {{on "click" (fn @close null)}}><span>Cancel</span></button> + <GhTaskButton + @buttonText="Regenerate {{capitalize @data.apiKey.type}} API Key" + @successText="Regenerated" + @task={{this.regenerateKeyTask}} + @class="gh-btn gh-btn-icon gh-btn-red" + /> + </div> +</div> \ No newline at end of file diff --git a/ghost/admin/app/components/settings/integrations/regenerate-key-modal.js b/ghost/admin/app/components/settings/integrations/regenerate-key-modal.js new file mode 100644 index 0000000000..5bb431426f --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/regenerate-key-modal.js @@ -0,0 +1,35 @@ +import Component from '@glimmer/component'; +import {capitalize} from '@ember/string'; +import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; +import {tracked} from '@glimmer/tracking'; + +export default class RegenerateKeyModal extends Component { + @service ajax; + @service ghostPaths; + @service notifications; + @service store; + + @tracked errorMessage; + + @task({drop: true}) + *regenerateKeyTask() { + const {integration, apiKey} = this.args.data; + const url = this.ghostPaths.url.api('/integrations/', integration.id, 'api_key', apiKey.id, 'refresh'); + + try { + const response = yield this.ajax.post(url, { + data: { + integrations: [{id: integration.id}] + } + }); + + this.store.pushPayload(response); + this.args.close(apiKey); + } catch (e) { + console.error(e); // eslint-disable-line + this.errorMessage = `There was an error regenerating the ${capitalize(apiKey.type)} API Key. Please try again`; + return; + } + } +} diff --git a/ghost/admin/app/controllers/settings/integration.js b/ghost/admin/app/controllers/settings/integration.js index 8726dd46f1..2a6f8f86fd 100644 --- a/ghost/admin/app/controllers/settings/integration.js +++ b/ghost/admin/app/controllers/settings/integration.js @@ -1,6 +1,7 @@ import Controller from '@ember/controller'; import DeleteIntegrationModal from '../../components/settings/integrations/delete-integration-modal'; import DeleteWebhookModal from '../../components/settings/integrations/delete-webhook-modal'; +import RegenerateKeyModal from '../../components/settings/integrations/regenerate-key-modal'; import config from 'ghost-admin/config/environment'; import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard'; import { @@ -23,9 +24,7 @@ export default class IntegrationController extends Controller { imageExtensions = IMAGE_EXTENSIONS; imageMimeTypes = IMAGE_MIME_TYPES; - @tracked showRegenerateKeyModal = false; - @tracked selectedApiKey = null; - @tracked isApiKeyRegenerated = false; + @tracked regeneratedApiKey = null; constructor() { super(...arguments); @@ -46,13 +45,6 @@ export default class IntegrationController extends Controller { return url.replace(/\/$/, ''); } - get regeneratedKeyType() { - if (this.isApiKeyRegenerated) { - return this.selectedApiKey.type; - } - return null; - } - get allWebhooks() { return this.store.peekAll('webhook'); } @@ -118,23 +110,13 @@ export default class IntegrationController extends Controller { } @action - confirmRegenerateKeyModal(apiKey, event) { + async confirmRegenerateKey(apiKey, event) { event?.preventDefault(); - this.showRegenerateKeyModal = true; - this.isApiKeyRegenerated = false; - this.selectedApiKey = apiKey; - } - - @action - cancelRegenerateKeyModal(event) { - event?.preventDefault(); - this.showRegenerateKeyModal = false; - } - - @action - regenerateKey(event) { - event?.preventDefault(); - this.isApiKeyRegenerated = true; + this.regeneratedApiKey = null; + this.regeneratedApiKey = await this.modals.open(RegenerateKeyModal, { + apiKey, + integration: this.integration + }); } @action diff --git a/ghost/admin/app/controllers/settings/integrations/zapier.js b/ghost/admin/app/controllers/settings/integrations/zapier.js index 9284865ea2..a49984e032 100644 --- a/ghost/admin/app/controllers/settings/integrations/zapier.js +++ b/ghost/admin/app/controllers/settings/integrations/zapier.js @@ -1,31 +1,29 @@ -import classic from 'ember-classic-decorator'; -import {action, computed} from '@ember/object'; -import {alias} from '@ember/object/computed'; -import {inject as service} from '@ember/service'; -/* eslint-disable ghost/ember/alias-model-in-controller */ import Controller from '@ember/controller'; +import RegenerateKeyModal from '../../../components/settings/integrations/regenerate-key-modal'; import config from 'ghost-admin/config/environment'; import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; import {task, timeout} from 'ember-concurrency'; +import {tracked} from '@glimmer/tracking'; -@classic export default class ZapierController extends Controller { @service ghostPaths; + @service modals; - selectedApiKey = null; - isApiKeyRegenerated = false; + @tracked regeneratedApiKey = null; - init() { - super.init(...arguments); + constructor() { + super(...arguments); if (this.isTesting === undefined) { this.isTesting = config.environment === 'test'; } } - @alias('model') - integration; + get integration() { + return this.model; + } - @computed get apiUrl() { let origin = window.location.origin; let subdir = this.ghostPaths.subdir; @@ -34,40 +32,28 @@ export default class ZapierController extends Controller { return url.replace(/\/$/, ''); } - @computed('isApiKeyRegenerated', 'selectedApiKey') - get regeneratedKeyType() { - if (this.isApiKeyRegenerated) { - return this.get('selectedApiKey.type'); - } - return null; - } - @action - confirmRegenerateKeyModal(apiKey) { - this.set('showRegenerateKeyModal', true); - this.set('isApiKeyRegenerated', false); - this.set('selectedApiKey', apiKey); + async confirmRegenerateKey(apiKey, event) { + event?.preventDefault(); + this.regeneratedApiKey = null; + this.regeneratedApiKey = await this.modals.open(RegenerateKeyModal, { + apiKey, + integration: this.integration, + internalIntegration: 'zapier' + }); } - @action - cancelRegenerateKeyModal() { - this.set('showRegenerateKeyModal', false); - } - - @action - regenerateKey() { - this.set('isApiKeyRegenerated', true); - } - - @task(function* () { + @task + *copyAdminKeyTask(event) { + event?.preventDefault(); copyTextToClipboard(this.integration.adminKey.secret); yield timeout(this.isTesting ? 50 : 3000); - }) - copyAdminKey; + } - @task(function* () { + @task + *copyApiUrlTask(event) { + event?.preventDefault(); copyTextToClipboard(this.apiUrl); yield timeout(this.isTesting ? 50 : 3000); - }) - copyApiUrl; + } } diff --git a/ghost/admin/app/routes/settings/integration.js b/ghost/admin/app/routes/settings/integration.js index 720c97ebf6..9223b72098 100644 --- a/ghost/admin/app/routes/settings/integration.js +++ b/ghost/admin/app/routes/settings/integration.js @@ -7,16 +7,6 @@ export default class IntegrationRoute extends AdminRoute { @service modals; @service router; - constructor() { - super(...arguments); - this.router.on('routeWillChange', () => { - if (this.controller) { - this.controller.set('selectedApiKey', null); - this.controller.set('isApiKeyRegenerated', false); - } - }); - } - model(params, transition) { // use the integrations controller to fetch all integrations and pick // out the one we want. Allows navigation back to integrations screen @@ -26,6 +16,10 @@ export default class IntegrationRoute extends AdminRoute { .integrationModelHook('id', params.integration_id, this, transition); } + resetController(controller) { + controller.regeneratedApiKey = null; + } + deactivate() { this.confirmModal = null; this.hasConfirmed = false; diff --git a/ghost/admin/app/routes/settings/integrations/zapier.js b/ghost/admin/app/routes/settings/integrations/zapier.js index adbf435914..f8311f506d 100644 --- a/ghost/admin/app/routes/settings/integrations/zapier.js +++ b/ghost/admin/app/routes/settings/integrations/zapier.js @@ -7,16 +7,6 @@ export default class ZapierRoute extends AdminRoute { @inject config; - constructor() { - super(...arguments); - this.router.on('routeWillChange', () => { - if (this.controller) { - this.controller.set('selectedApiKey', null); - this.controller.set('isApiKeyRegenerated', false); - } - }); - } - beforeModel() { super.beforeModel(...arguments); @@ -34,6 +24,10 @@ export default class ZapierRoute extends AdminRoute { .integrationModelHook('slug', 'zapier', this, transition); } + resetController(controller) { + controller.regeneratedApiKey = null; + } + buildRouteInfoMetadata() { return { titleToken: 'Zapier' diff --git a/ghost/admin/app/styles/layouts/apps.css b/ghost/admin/app/styles/layouts/apps.css index 71eec4adb7..2f751008d2 100644 --- a/ghost/admin/app/styles/layouts/apps.css +++ b/ghost/admin/app/styles/layouts/apps.css @@ -440,7 +440,6 @@ .gh-zapier-data-container .gh-zapier-data { display: flex; - align-items: center; } @media (max-width: 500px) { @@ -463,14 +462,13 @@ .gh-zapier-data .data { width: 100%; - height: 28px; padding: 4px; color: var(--darkgrey); font-size: 1.4rem; line-height: 1.45; font-weight: 500; border-radius: 3px; - overflow: hidden; + overflow-x: hidden; } .gh-zapier-data .data.highlight-hover:hover { diff --git a/ghost/admin/app/templates/settings/integration.hbs b/ghost/admin/app/templates/settings/integration.hbs index 3ccba2fa81..db05262762 100644 --- a/ghost/admin/app/templates/settings/integration.hbs +++ b/ghost/admin/app/templates/settings/integration.hbs @@ -87,7 +87,7 @@ @class="flex flex-column w-100 mr3" @errors={{this.integration.errors}} @hasValidated={{this.integration.hasValidated}} - @property="decription" + @property="description" > <label for="integration_description" class="mt3">Description</label> <GhTextInput @@ -112,20 +112,20 @@ {{this.integration.contentKey.secret}} </span> <div class="app-api-buttons child"> - <button type="button" {{on "click" (fn this.confirmRegenerateKeyModal this.integration.contentKey)}} class="app-button-regenerate" data-tooltip="Regenerate"> + <button type="button" {{on "click" (fn this.confirmRegenerateKey this.integration.contentKey)}} class="app-button-regenerate" data-tooltip="Regenerate"> {{svg-jar "reload" class="w4 h4 stroke-midgrey"}} </button> <button type="button" {{on "click" (perform this.copyContentKey)}} class="app-button-copy"> {{#if this.copyContentKey.isRunning}} - {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied + {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied {{else}} - Copy + Copy {{/if}} </button> </div> </div> - {{#if (eq this.regeneratedKeyType this.integration.contentKey.type)}} - <div class="green nt3 mb2"> Content API Key was successfully regenerated </div> + {{#if (eq this.regeneratedApiKey.type this.integration.contentKey.type)}} + <div class="green"> Content API Key was successfully regenerated </div> {{/if}} </td> </tr> @@ -137,19 +137,19 @@ {{this.integration.adminKey.secret}} </span> <div class="app-api-buttons child"> - <button type="button" {{on "click" (fn this.confirmRegenerateKeyModal this.integration.adminKey)}} class="app-button-regenerate" data-tooltip="Regenerate"> + <button type="button" {{on "click" (fn this.confirmRegenerateKey this.integration.adminKey)}} class="app-button-regenerate" data-tooltip="Regenerate"> {{svg-jar "reload" class="w4 h4 stroke-midgrey"}} </button> <button type="button" {{on "click" (perform this.copyAdminKey)}} class="app-button-copy"> {{#if this.copyAdminKey.isRunning}} - {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied + {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied {{else}} - Copy + Copy {{/if}} </button> </div> </div> - {{#if (eq this.regeneratedKeyType this.integration.adminKey.type)}} + {{#if (eq this.regeneratedApiKey.type this.integration.adminKey.type)}} <div class="green"> Admin API key was successfully regenerated </div> {{/if}} </td> @@ -280,15 +280,4 @@ </section> </section> -{{#if this.showRegenerateKeyModal}} - <GhFullscreenModal @modal="regenerate-key" - @model={{hash - apiKey=this.selectedApiKey - integration=this.integration - }} - @confirm={{this.regenerateKey}} - @close={{this.cancelRegenerateKeyModal}} - @modifier="action wide" /> -{{/if}} - {{outlet}} diff --git a/ghost/admin/app/templates/settings/integrations/zapier.hbs b/ghost/admin/app/templates/settings/integrations/zapier.hbs index 3cc2a9615a..48df088829 100644 --- a/ghost/admin/app/templates/settings/integrations/zapier.hbs +++ b/ghost/admin/app/templates/settings/integrations/zapier.hbs @@ -20,7 +20,7 @@ <div class="gh-main-section-block overflow-hidden"> <div class="gh-main-section-content app-detail-heading app-grid"> <div class="app-cell"> - <img class="app-icon" src="assets/img/zapier.svg" /> + <img class="app-icon" src="assets/img/zapier.svg" alt="" role="presentation" /> </div> <div class="app-cell overflow-hidden"> <h3>Zapier</h3> @@ -30,24 +30,24 @@ <div class="gh-zapier-data"> <div class="data-label">Admin API key</div> <div class="data highlight-hover"> - <div class="relative flex items-center {{unless this.copyAdminKey.isRunning "hide-child-instant"}}"> + <div class="relative flex items-center {{unless this.copyAdminKeyTask.isRunning "hide-child-instant"}}"> <span class="admin-key" data-test-text="admin-key"> {{this.integration.adminKey.secret}} </span> <div class="app-api-buttons child"> - <button type="button" {{action "confirmRegenerateKeyModal" this.integration.adminKey}} class="app-button-regenerate"> + <button type="button" {{on "click" (fn this.confirmRegenerateKey this.integration.adminKey)}} class="app-button-regenerate"> {{svg-jar "reload" class="w4 h4 stroke-midgrey"}} </button> - <button type="button" {{action (perform this.copyAdminKey)}} class="app-button-copy"> - {{#if this.copyAdminKey.isRunning}} - {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied + <button type="button" {{on "click" (perform this.copyAdminKeyTask)}} class="app-button-copy"> + {{#if this.copyAdminKeyTask.isRunning}} + {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied {{else}} - Copy + Copy {{/if}} </button> </div> </div> - {{#if (eq this.regeneratedKeyType this.integration.adminKey.type)}} + {{#if (eq this.regeneratedApiKey.type this.integration.adminKey.type)}} <div class="green"> Admin API Key was successfully regenerated </div> {{/if}} </div> @@ -55,14 +55,14 @@ <div class="gh-zapier-data"> <div class="data-label">API URL</div> <div class="data highlight-hover"> - <div class="relative flex items-center {{unless this.copyApiUrl.isRunning "hide-child-instant"}}"> + <div class="relative flex items-center {{unless this.copyApiUrlTask.isRunning "hide-child-instant"}}"> <span class="api-url" data-test-text="api-url"> {{this.apiUrl}} </span> <div class="app-api-buttons child"> - <button type="button" {{action (perform this.copyApiUrl)}} class="app-button-copy"> - {{#if this.copyApiUrl.isRunning}} - {{svg-jar "check-circle" class="w3 v-mid mr2"}} Copied + <button type="button" {{on "click" (perform this.copyApiUrlTask)}} class="app-button-copy"> + {{#if this.copyApiUrlTask.isRunning}} + {{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied {{else}} Copy {{/if}} @@ -247,15 +247,3 @@ </section> </section> </section> - -{{#if this.showRegenerateKeyModal}} - <GhFullscreenModal @modal="regenerate-key" - @model={{hash - apiKey=this.selectedApiKey - integration=this.integration - internalIntegration="zapier" - }} - @confirm={{action "regenerateKey"}} - @close={{action "cancelRegenerateKeyModal"}} - @modifier="action wide" /> -{{/if}}