Added custom integrations - Content API Keys and "Site rebuild" webhooks (#1063)

closes TryGhost/Ghost#9942
- move custom integrations UI out from behind the developer experiments flag
- put Admin API key and web hook secret fields behind the developer experiments flag
- do not show "unsaved changes" modal when adding/editing a webhook
- fixed all webhooks showing for each custom integration
This commit is contained in:
Kevin Ansfield 2018-11-06 10:59:47 +00:00 committed by GitHub
parent 7308fcba79
commit ac17053863
7 changed files with 112 additions and 98 deletions

View File

@ -8,6 +8,7 @@ import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default ModalComponent.extend({
config: service(),
router: service(),
availableEvents: null,

View File

@ -6,9 +6,12 @@ import {
import {alias} from '@ember/object/computed';
import {computed} from '@ember/object';
import {htmlSafe} from '@ember/string';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
export default Controller.extend({
config: service(),
imageExtensions: IMAGE_EXTENSIONS,
imageMimeTypes: IMAGE_MIME_TYPES,
@ -18,7 +21,7 @@ export default Controller.extend({
return this.store.peekAll('webhook');
}),
filteredWebhooks: computed('allWebhooks.@each.{isNew,isDeleted}', function () {
filteredWebhooks: computed('integration.id', 'allWebhooks.@each.{isNew,isDeleted}', function () {
return this.allWebhooks.filter((webhook) => {
let matchesIntegration = webhook.belongsTo('integration').id() === this.integration.id;

View File

@ -5,7 +5,6 @@ import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default Controller.extend({
config: service(),
settings: service(),
store: service(),

View File

@ -46,7 +46,16 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
willTransition(transition) {
let {controller} = this;
if (!controller.integration.isDeleted && controller.integration.hasDirtyAttributes) {
// check to see if we're navigating away from the custom integration
// route - we want to allow editing webhooks without showing the
// "unsaved changes" confirmation modal
let isExternalRoute =
// allow sub-routes of settings.integration
!transition.targetName.match(/^settings\.integration\./)
// do not allow changes in integration
|| transition.params['settings.integration'].integration_id !== controller.integration.id;
if (isExternalRoute && !controller.integration.isDeleted && controller.integration.hasDirtyAttributes) {
transition.abort();
controller.send('toggleUnsavedChangesModal', transition);
return;

View File

@ -61,24 +61,26 @@
{{gh-error-message errors=webhook.errors property="targetUrl" data-test-error="new-webhook-targetUrl"}}
{{/gh-form-group}}
</fieldset>
<fieldset>
{{#gh-form-group errors=webhook.errors hasValidated=webhook.hasValidated property="secret"}}
<label for="new-webhook-secret" class="fw6">Secret</label>
{{gh-text-input
value=(readonly webhook.secret)
oninput=(action (mut webhook.secret) value="target.value")
focus-out=(action "validate" "secret" target=webhook)
id="new-webhook-secret"
name="secret"
class="gh-input mt1"
placeholder="Webhook secret..."
autofocus="autofocus"
autocapitalize="off"
autocorrect="off"
data-test-input="new-webhook-secret"}}
{{gh-error-message errors=webhook.errors property="secret" data-test-error="new-webhook-secret"}}
{{/gh-form-group}}
</fieldset>
{{#if config.enableDeveloperExperiments}}
<fieldset>
{{#gh-form-group errors=webhook.errors hasValidated=webhook.hasValidated property="secret"}}
<label for="new-webhook-secret" class="fw6">Secret</label>
{{gh-text-input
value=(readonly webhook.secret)
oninput=(action (mut webhook.secret) value="target.value")
focus-out=(action "validate" "secret" target=webhook)
id="new-webhook-secret"
name="secret"
class="gh-input mt1"
placeholder="Webhook secret..."
autofocus="autofocus"
autocapitalize="off"
autocorrect="off"
data-test-input="new-webhook-secret"}}
{{gh-error-message errors=webhook.errors property="secret" data-test-error="new-webhook-secret"}}
{{/gh-form-group}}
</fieldset>
{{/if}}
{{#if error}}
<p class="red">{{error}}</p>
{{/if}}

View File

@ -124,36 +124,38 @@
</div>
{{/gh-validation-status-container}}
{{/with}}
{{#with integration.adminKey as |adminKey|}}
{{#gh-validation-status-container class="flex flex-column w-100 ml3"}}
<div class="flex">
<label for="admin_key" class="flex-grow-1 darkgrey fw7 f8">
Admin API Key
</label>
<span class="db f8 midgrey">
{{#if copyAdminKey.isRunning}}
Copied to clipboard
{{else}}
{{adminKey.lastSeenAtUTC}}
{{/if}}
</span>
</div>
<div class="relative hide-child mt1">
<input id="admin_key"
class="w-100 pa3 bg-whitegrey-l2 midlightgrey ba b--whitegrey br3"
type="text"
value={{adminKey.secret}}
disabled="true"
data-test-input="admin_key">
{{#if config.enableDeveloperExperiments}}
{{#with integration.adminKey as |adminKey|}}
{{#gh-validation-status-container class="flex flex-column w-100 ml3"}}
<div class="flex">
<label for="admin_key" class="flex-grow-1 darkgrey fw7 f8">
Admin API Key
</label>
<span class="db f8 midgrey">
{{#if copyAdminKey.isRunning}}
Copied to clipboard
{{else}}
{{adminKey.lastSeenAtUTC}}
{{/if}}
</span>
</div>
<div class="relative hide-child mt1">
<input id="admin_key"
class="w-100 pa3 bg-whitegrey-l2 midlightgrey ba b--whitegrey br3"
type="text"
value={{adminKey.secret}}
disabled="true"
data-test-input="admin_key">
<div class="absolute top-0 right-1">
<div class="pt1 pr3 pb1 pl3 bg-black-70 child br3 f8 nudge-top--6 nudge-right--1">
<button type="button" {{action (perform copyAdminKey)}} class="white fw4">Copy</button>
<div class="absolute top-0 right-1">
<div class="pt1 pr3 pb1 pl3 bg-black-70 child br3 f8 nudge-top--6 nudge-right--1">
<button type="button" {{action (perform copyAdminKey)}} class="white fw4">Copy</button>
</div>
</div>
</div>
</div>
{{/gh-validation-status-container}}
{{/with}}
{{/gh-validation-status-container}}
{{/with}}
{{/if}}
</div>
<h4 class="mt15 midgrey f7 fw4">Webhooks</h4>

View File

@ -101,59 +101,57 @@
</div>
</section>
{{#if config.enableDeveloperExperiments}}
<section class="apps-grid-container pt6">
<div class="flex flex-row items-center pb2">
<span class="dib flex-grow-1 midgrey">Custom integrations</span>
{{#link-to "settings.integrations.new" class="gh-btn gh-btn-green" data-test-button="new-integration"}}
<span>Add custom integration</span>
{{/link-to}}
</div>
<section class="apps-grid-container pt6">
<div class="flex flex-row items-center pb2">
<span class="dib flex-grow-1 midgrey">Custom integrations</span>
{{#link-to "settings.integrations.new" class="gh-btn gh-btn-green" data-test-button="new-integration"}}
<span>Add custom integration</span>
{{/link-to}}
</div>
<div class="apps-grid">
{{#each integrations as |integration|}}
<div class="apps-grid-cell" data-test-custom-integration>
{{#link-to "settings.integration" integration data-test-integration=integration.id}}
<article class="apps-card-app">
<div class="apps-card-left">
<figure class="apps-card-app-icon flex items-center" style={{integration-icon-style integration}}>
{{#unless integration.iconImage}}
{{svg-jar "integration" class="w-100 stroke-darkgrey"}}
{{/unless}}
</figure>
<div class="apps-card-meta">
<h3 class="apps-card-app-title" data-test-text="name">
{{integration.name}}
</h3>
<p class="apps-card-app-desc" data-test-text="description">
{{integration.description}}
</p>
</div>
<div class="apps-grid">
{{#each integrations as |integration|}}
<div class="apps-grid-cell" data-test-custom-integration>
{{#link-to "settings.integration" integration data-test-integration=integration.id}}
<article class="apps-card-app">
<div class="apps-card-left">
<figure class="apps-card-app-icon flex items-center" style={{integration-icon-style integration}}>
{{#unless integration.iconImage}}
{{svg-jar "integration" class="w-100 stroke-darkgrey"}}
{{/unless}}
</figure>
<div class="apps-card-meta">
<h3 class="apps-card-app-title" data-test-text="name">
{{integration.name}}
</h3>
<p class="apps-card-app-desc" data-test-text="description">
{{integration.description}}
</p>
</div>
<div class="gh-card-right">
<div class="apps-configured">
<span>Configure</span>
{{svg-jar "arrow-right"}}
</div>
</div>
<div class="gh-card-right">
<div class="apps-configured">
<span>Configure</span>
{{svg-jar "arrow-right"}}
</div>
</article>
{{/link-to}}
</div>
{{else}}
<div class="flex flex-column justify-center items-center mih40 miw-100" data-test-blank="custom-integrations">
{{#if fetchIntegrations.isRunning}}
<div class="gh-loading-spinner"></div>
{{else}}
<p class="ma0 pa0 tc midgrey">
Use API keys and webhooks to create custom integrations.<br>
No custom integrations.
</p>
{{/if}}
</div>
{{/each}}
</div>
</section>
{{/if}}
</div>
</article>
{{/link-to}}
</div>
{{else}}
<div class="flex flex-column justify-center items-center mih40 miw-100" data-test-blank="custom-integrations">
{{#if fetchIntegrations.isRunning}}
<div class="gh-loading-spinner"></div>
{{else}}
<p class="ma0 pa0 tc midgrey">
Use API keys and webhooks to create custom integrations.<br>
No custom integrations.
</p>
{{/if}}
</div>
{{/each}}
</div>
</section>
</section>
{{outlet}}