Completed Octane migration of integration controller

refs https://github.com/TryGhost/Ghost/issues/14101

Applied scout rule to fix linter errors when touching a file.

- added local `updateProperty` and `validateProperty` methods to remove use of `target=`
  - our validation implementation does not properly bind `this` so direct usage of `this.integration.validate` fails
- added `on-input` argument support to our `TextInput` mixin so we we're not overwriting the native event handlers on classic input components
This commit is contained in:
Kevin Ansfield 2022-02-21 17:54:33 +00:00
parent d1e7ccbe00
commit f44c54862b
4 changed files with 120 additions and 74 deletions

View File

@ -1839,3 +1839,26 @@ add|ember-template-lint|no-nested-landmark|131|24|131|24|9eb7d301f1f50334e793aaf
remove|ember-template-lint|table-groups|5|8|5|8|9b53737f259340d2970119c70be1af3a83081cab|1643760000000|1646352000000|1648940400000|app/components/gh-portal-links.hbs
remove|ember-template-lint|no-action|11|72|11|72|a8b13fedc345ad6ca2c1514b209e84a19d82915d|1643760000000|1646352000000|1648940400000|app/components/gh-portal-links.hbs
remove|ember-template-lint|no-invalid-interactive|11|64|11|64|5b5bb32075e5f0470fbe12d099229964f1dc7dfe|1643760000000|1646352000000|1648940400000|app/components/gh-portal-links.hbs
remove|ember-template-lint|no-action|32|48|32|48|993079a3ad8b556330d2aa5a0cb01dcb63a4bdb0|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|43|44|43|44|166ab6daac6556b497c8693a94715fca7c8158f9|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|71|43|71|43|8234e3f75b9c72cbd8e726b3d26f03d0a2a26dfb|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|72|47|72|47|829a527ba5a2ff30234ea240400d9935421c5556|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|90|43|90|43|ffe817ddea62969f8b2d45127828416d6adfc773|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|91|47|91|47|bb670279bd6dc2dfca1b714200cc42b1b4b38d69|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|107|74|107|74|159455060ac247ee60be18b8ebb8f7907f652132|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|110|74|110|74|38bfdf3e4ec8c79d195e80a48c2f343757845cab|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|132|74|132|74|2e441eb8956fdd6684b75ae1139f097237ebc1fa|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|135|74|135|74|2d51bed033fe50baea3002bf5acc234ac3738b58|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|157|74|157|74|bfcab210b651dbe74a1b9e5eb565756ab863f3bb|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-passed-in-event-handlers|71|36|71|36|094eea0b41531dd31234f4a47d22faa0ad3cbaa7|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-passed-in-event-handlers|90|36|90|36|ecfb8bbcc595aaaf0c1a929efa948e75787a5e18|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|202|92|202|92|bb9a5097405a3fcaad689d0d592294e1741edd7b|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|246|72|246|72|541a413eb944fa0091ea28f7256a20e2a724a3a9|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|255|17|255|17|9fa650a0ca3325ef8182634ad5d34c5db548287a|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|256|15|256|15|2fe6989da74d54cbdbaef0ae0a7cc68f22825a1d|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|266|17|266|17|1907c9b1ae81cfb450b7a2ae7e0f874c26595144|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|267|15|267|15|20eb710122c95e74c9293fe14e11f105a86f172a|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|273|17|273|17|17ede810216f1750e8f5a0447f0d8ca9dd51eefd|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|274|15|274|15|97a8488c705fd167d8803635395470e929e57425|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|280|17|280|17|989fbbea271b49165ed76435745c43151cfc7ef7|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs
remove|ember-template-lint|no-action|281|15|281|15|6466c92ea49ac23d994c08c504e6d4ffefcadbff|1643760000000|1646352000000|1648940400000|app/templates/settings/integration.hbs

View File

@ -1,39 +1,41 @@
import Controller from '@ember/controller';
import classic from 'ember-classic-decorator';
import config from 'ghost-admin/config/environment';
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
import {
IMAGE_EXTENSIONS,
IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {action, computed} from '@ember/object';
import {alias} from '@ember/object/computed';
import {action} from '@ember/object';
import {htmlSafe} from '@ember/template';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
@classic
export default class IntegrationController extends Controller {
@service config;
@service ghostPaths;
imageExtensions = IMAGE_EXTENSIONS;
imageMimeTypes = IMAGE_MIME_TYPES;
showRegenerateKeyModal = false;
selectedApiKey = null;
isApiKeyRegenerated = false;
init() {
super.init(...arguments);
@tracked showDeleteIntegrationModal = false;
@tracked showRegenerateKeyModal = false;
@tracked showUnsavedChangesModal = false;
@tracked selectedApiKey = null;
@tracked isApiKeyRegenerated = false;
@tracked webhookToDelete;
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;
@ -42,20 +44,17 @@ export default class IntegrationController extends Controller {
return url.replace(/\/$/, '');
}
@computed('isApiKeyRegenerated', 'selectedApiKey')
get regeneratedKeyType() {
if (this.isApiKeyRegenerated) {
return this.get('selectedApiKey.type');
return this.selectedApiKey.type;
}
return null;
}
@computed
get allWebhooks() {
return this.store.peekAll('webhook');
}
@computed('integration.id', 'allWebhooks.@each.{isNew,isDeleted}')
get filteredWebhooks() {
return this.allWebhooks.filter((webhook) => {
let matchesIntegration = webhook.belongsTo('integration').id() === this.integration.id;
@ -66,7 +65,6 @@ export default class IntegrationController extends Controller {
});
}
@computed('integration.iconImage')
get iconImageStyle() {
let url = this.integration.iconImage;
if (url) {
@ -83,11 +81,22 @@ export default class IntegrationController extends Controller {
}
@action
triggerIconFileDialog() {
triggerIconFileDialog(event) {
event.preventDefault();
let input = document.querySelector('input[type="file"][name="iconImage"]');
input.click();
}
@action
updateProperty(property, event) {
this.integration.set(property, event.target.value);
}
@action
validateProperty(property) {
this.integration.validate({property});
}
@action
setIconImage([image]) {
this.integration.set('iconImage', image.url);
@ -103,13 +112,13 @@ export default class IntegrationController extends Controller {
let leaveTransition = this.leaveScreenTransition;
if (!transition && this.showUnsavedChangesModal) {
this.set('leaveScreenTransition', null);
this.set('showUnsavedChangesModal', false);
this.leaveScreenTransition = null;
this.showUnsavedChangesModal = false;
return;
}
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
this.set('leaveScreenTransition', transition);
this.leaveScreenTransition = transition;
// if a save is running, wait for it to finish then transition
if (this.saveTask.isRunning) {
@ -119,12 +128,13 @@ export default class IntegrationController extends Controller {
}
// we genuinely have unsaved data, show the modal
this.set('showUnsavedChangesModal', true);
this.showUnsavedChangesModal = true;
}
}
@action
leaveScreen() {
leaveScreen(event) {
event?.preventDefault();
let transition = this.leaveScreenTransition;
if (!transition) {
@ -139,53 +149,63 @@ export default class IntegrationController extends Controller {
}
@action
deleteIntegration() {
deleteIntegration(event) {
event?.preventDefault();
this.integration.destroyRecord();
}
@action
confirmIntegrationDeletion() {
this.set('showDeleteIntegrationModal', true);
confirmIntegrationDeletion(event) {
event?.preventDefault();
this.showDeleteIntegrationModal = true;
}
@action
cancelIntegrationDeletion() {
this.set('showDeleteIntegrationModal', false);
cancelIntegrationDeletion(event) {
event?.preventDefault();
this.showDeleteIntegrationModal = false;
}
@action
confirmRegenerateKeyModal(apiKey) {
this.set('showRegenerateKeyModal', true);
this.set('isApiKeyRegenerated', false);
this.set('selectedApiKey', apiKey);
confirmRegenerateKeyModal(apiKey, event) {
event?.preventDefault();
this.showRegenerateKeyModal = true;
this.isApiKeyRegenerated = false;
this.selectedApiKey = apiKey;
}
@action
cancelRegenerateKeyModal() {
this.set('showRegenerateKeyModal', false);
cancelRegenerateKeyModal(event) {
event?.preventDefault();
this.showRegenerateKeyModal = false;
}
@action
regenerateKey() {
this.set('isApiKeyRegenerated', true);
regenerateKey(event) {
event?.preventDefault();
this.isApiKeyRegenerated = true;
}
@action
confirmWebhookDeletion(webhook) {
this.set('webhookToDelete', webhook);
confirmWebhookDeletion(webhook, event) {
event?.preventDefault();
this.webhookToDelete = webhook;
}
@action
cancelWebhookDeletion() {
this.set('webhookToDelete', null);
cancelWebhookDeletion(event) {
event?.preventDefault();
this.webhookToDelete = null;
}
@action
deleteWebhook() {
deleteWebhook(event) {
event?.preventDefault();
return this.webhookToDelete.destroyRecord();
}
@task(function* () {
@task
*saveTask() {
try {
return yield this.integration.save();
} catch (e) {
@ -196,24 +216,23 @@ export default class IntegrationController extends Controller {
throw e;
}
})
saveTask;
}
@task(function* () {
@task
*copyContentKey() {
copyTextToClipboard(this.integration.contentKey.secret);
yield timeout(this.isTesting ? 50 : 3000);
})
copyContentKey;
}
@task(function* () {
@task
*copyAdminKey() {
copyTextToClipboard(this.integration.adminKey.secret);
yield timeout(this.isTesting ? 50 : 3000);
})
copyAdminKey;
}
@task(function* () {
@task
*copyApiUrl() {
copyTextToClipboard(this.apiUrl);
yield timeout(this.isTesting ? 50 : 3000);
})
copyApiUrl;
}
}

View File

@ -35,6 +35,10 @@ export default Mixin.create({
}
},
input(event) {
this['on-input']?.(event);
},
keyDown(event) {
// stop event propagation when pressing "enter"
// most useful in the case when undesired (global) keyboard shortcuts

View File

@ -29,7 +29,7 @@
<GhUploader
@extensions={{this.imageExtensions}}
@onComplete={{action "setIconImage"}}
@onComplete={{this.setIconImage}}
as |uploader|
>
{{#if uploader.isUploading}}
@ -40,7 +40,7 @@
<button
type="button"
class="child app-custom-icon-uploadlabel"
{{action "triggerIconFileDialog"}}
{{on "click" this.triggerIconFileDialog}}
>
Upload
</button>
@ -68,8 +68,8 @@
@class="gh-input mt1 mb1"
@type="text"
@value={{readonly this.integration.name}}
@input={{action (mut this.integration.name) value="target.value"}}
@focus-out={{action "validate" "name" target=this.integration}}
@on-input={{fn this.updateProperty "name"}}
@focus-out={{fn this.validateProperty "name"}}
data-test-input="name"
/>
<GhErrorMessage @errors={{this.integration.errors}} @property="name" data-test-error="name" class="ma0" />
@ -87,8 +87,8 @@
@class="gh-input mt1"
@type="text"
@value={{readonly this.integration.description}}
@input={{action (mut this.integration.description) value="target.value"}}
@focus-out={{action "validate" "description" target=this.integration}}
@on-input={{fn this.updateProperty "description"}}
@focus-out={{fn this.validateProperty "description"}}
data-test-input="description"
/>
<GhErrorMessage @errors={{this.integration.errors}} @property="description" data-test-error="description" class="ma0" />
@ -104,10 +104,10 @@
{{this.integration.contentKey.secret}}
</span>
<div class="app-api-buttons child">
<button type="button" {{action "confirmRegenerateKeyModal" this.integration.contentKey}} class="app-button-regenerate" data-tooltip="Regenerate">
<button type="button" {{on "click" (fn this.confirmRegenerateKeyModal this.integration.contentKey)}} class="app-button-regenerate" data-tooltip="Regenerate">
{{svg-jar "reload" class="w4 h4 stroke-midgrey"}}
</button>
<button type="button" {{action (perform this.copyContentKey)}} class="app-button-copy">
<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
{{else}}
@ -129,10 +129,10 @@
{{this.integration.adminKey.secret}}
</span>
<div class="app-api-buttons child">
<button type="button" {{action "confirmRegenerateKeyModal" this.integration.adminKey}} class="app-button-regenerate" data-tooltip="Regenerate">
<button type="button" {{on "click" (fn this.confirmRegenerateKeyModal this.integration.adminKey)}} class="app-button-regenerate" data-tooltip="Regenerate">
{{svg-jar "reload" class="w4 h4 stroke-midgrey"}}
</button>
<button type="button" {{action (perform this.copyAdminKey)}} class="app-button-copy">
<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
{{else}}
@ -154,7 +154,7 @@
{{this.apiUrl}}
</span>
<div class="app-api-buttons child">
<button type="button" {{action (perform this.copyApiUrl)}} class="app-button-copy">
<button type="button" {{on "click" (perform this.copyApiUrl)}} class="app-button-copy">
{{#if this.copyApiUrl.isRunning}}
{{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied
{{else}}
@ -199,7 +199,7 @@
<LinkTo @route="settings.integration.webhooks.edit" @models={{array this.integration webhook}} data-test-link="edit-webhook">
{{svg-jar "pen" class="w6 h6 fill-midgrey pa1 mr1"}}
</LinkTo>
<button data-test-button="delete-webhook" type="button" {{action "confirmWebhookDeletion" webhook}}>
<button data-test-button="delete-webhook" type="button" {{on "click" (fn this.confirmWebhookDeletion webhook)}}>
{{svg-jar "trash" class="w6 fill-red pa1"}}
</button>
</div>
@ -243,7 +243,7 @@
<section class="gh-main-section">
<div class="gh-main-section-block">
<button class="gh-btn gh-btn-red gh-btn-icon" type="button" {{action "confirmIntegrationDeletion"}}>
<button class="gh-btn gh-btn-red gh-btn-icon" type="button" {{on "click" this.confirmIntegrationDeletion}}>
<span> Delete integration </span>
</button>
</div>
@ -252,8 +252,8 @@
{{#if this.showUnsavedChangesModal}}
<GhFullscreenModal @modal="leave-settings"
@confirm={{action "leaveScreen"}}
@close={{action "toggleUnsavedChangesModal"}}
@confirm={{this.leaveScreen}}
@close={{this.toggleUnsavedChangesModal}}
@modifier="action wide" />
{{/if}}
@ -263,22 +263,22 @@
apiKey=this.selectedApiKey
integration=this.integration
}}
@confirm={{action "regenerateKey"}}
@close={{action "cancelRegenerateKeyModal"}}
@confirm={{this.regenerateKey}}
@close={{this.cancelRegenerateKeyModal}}
@modifier="action wide" />
{{/if}}
{{#if this.showDeleteIntegrationModal}}
<GhFullscreenModal @modal="delete-integration"
@confirm={{action "deleteIntegration"}}
@close={{action "cancelIntegrationDeletion"}}
@confirm={{this.deleteIntegration}}
@close={{this.cancelIntegrationDeletion}}
@modifier="action wide" />
{{/if}}
{{#if this.webhookToDelete}}
<GhFullscreenModal @modal="delete-webhook"
@confirm={{action "deleteWebhook"}}
@close={{action "cancelWebhookDeletion"}}
@confirm={{this.deleteWebhook}}
@close={{this.cancelWebhookDeletion}}
@modifier="action wide" />
{{/if}}