0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-18 02:21:47 -05:00

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
This commit is contained in:
Kevin Ansfield 2022-11-14 09:41:09 +00:00
parent 283babe574
commit f92c62b59a
12 changed files with 136 additions and 201 deletions

View file

@ -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

View file

@ -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>

View file

@ -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()
});

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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'

View file

@ -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 {

View file

@ -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}}

View file

@ -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}}