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

View File

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