UI/kv codemirror diff (#13000)
* staying with jsondiff * routing setup * send compare against data to component after using new adapater method to return the version data. * functionality * fix issue on route transition not calling model hook * formatting * update version * changelog * glimmerize the json-editor component * catch up * passing tracked property from child to parent * pull out of jsonEditor * fix issue with message * icon * fix some issues with right selection * changes and convert to component * integration test * tests * fixes * cleanup * cleanup 2 * fixes * fix test by spread attributes * remove log * remove
This commit is contained in:
parent
82d49a08fb
commit
43148ed9f2
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: Add version diff view for KV V2
|
||||||
|
```
|
|
@ -54,6 +54,16 @@ export default ApplicationAdapter.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
querySecretDataByVersion(id) {
|
||||||
|
return this.ajax(this.urlForQueryRecord(id), 'GET')
|
||||||
|
.then(resp => {
|
||||||
|
return resp.data;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
return error.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
urlForCreateRecord(modelName, snapshot) {
|
urlForCreateRecord(modelName, snapshot) {
|
||||||
let backend = snapshot.belongsTo('secret').belongsTo('engine').id;
|
let backend = snapshot.belongsTo('secret').belongsTo('engine').id;
|
||||||
let path = snapshot.attr('path');
|
let path = snapshot.attr('path');
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module DiffVersionSelector
|
||||||
|
* DiffVersionSelector component includes a toolbar and diff view between KV 2 versions. It uses the library jsondiffpatch.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* <DiffVersionSelector @model={model}/>
|
||||||
|
* ```
|
||||||
|
* @param {object} model - model that comes from secret-v2-version
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class DiffVersionSelector extends Component {
|
||||||
|
@tracked leftSideVersionDataSelected = null;
|
||||||
|
@tracked leftSideVersionSelected = null;
|
||||||
|
@tracked rightSideVersionDataSelected = null;
|
||||||
|
@tracked rightSideVersionSelected = null;
|
||||||
|
@tracked statesMatch = false;
|
||||||
|
@tracked visualDiff = null;
|
||||||
|
@service store;
|
||||||
|
|
||||||
|
adapter = this.store.adapterFor('secret-v2-version');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.createVisualDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
get leftSideDataInit() {
|
||||||
|
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${this.args.model.currentVersion}"]`;
|
||||||
|
return this.adapter
|
||||||
|
.querySecretDataByVersion(string)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(() => null);
|
||||||
|
}
|
||||||
|
get rightSideDataInit() {
|
||||||
|
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${this.rightSideVersionInit}"]`;
|
||||||
|
return this.adapter
|
||||||
|
.querySecretDataByVersion(string)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(() => null);
|
||||||
|
}
|
||||||
|
get rightSideVersionInit() {
|
||||||
|
// initial value of right side version is one less than the current version
|
||||||
|
return this.args.model.currentVersion === 1 ? 0 : this.args.model.currentVersion - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createVisualDiff() {
|
||||||
|
let diffpatcher = jsondiffpatch.create({});
|
||||||
|
let leftSideVersionData = this.leftSideVersionDataSelected || (await this.leftSideDataInit);
|
||||||
|
let rightSideVersionData = this.rightSideVersionDataSelected || (await this.rightSideDataInit);
|
||||||
|
let delta = diffpatcher.diff(rightSideVersionData, leftSideVersionData);
|
||||||
|
if (delta === undefined) {
|
||||||
|
this.statesMatch = true;
|
||||||
|
this.visualDiff = JSON.stringify(leftSideVersionData, undefined, 2); // params: value, replacer (all properties included), space (white space and indentation, line break, etc.)
|
||||||
|
} else {
|
||||||
|
this.statesMatch = false;
|
||||||
|
this.visualDiff = jsondiffpatch.formatters.html.format(delta, rightSideVersionData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async selectVersion(selectedVersion, actions, side) {
|
||||||
|
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${selectedVersion}"]`;
|
||||||
|
let secretData = await this.adapter.querySecretDataByVersion(string);
|
||||||
|
if (side === 'left') {
|
||||||
|
this.leftSideVersionDataSelected = secretData.data;
|
||||||
|
this.leftSideVersionSelected = selectedVersion;
|
||||||
|
}
|
||||||
|
if (side === 'right') {
|
||||||
|
this.rightSideVersionDataSelected = secretData.data;
|
||||||
|
this.rightSideVersionSelected = selectedVersion;
|
||||||
|
}
|
||||||
|
await this.createVisualDiff();
|
||||||
|
// close dropdown menu.
|
||||||
|
actions.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,21 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module JsonEditor
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* <JsonEditor @title="Policy" @value={{codemirror.string}} @valueUpdated={{ action "codemirrorUpdate"}} />
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {string} [title] - Name above codemirror view
|
||||||
|
* @param {string} value - a specific string the comes from codemirror. It's the value inside the codemirror display
|
||||||
|
* @param {Function} [valueUpdated] - action to preform when you edit the codemirror value.
|
||||||
|
* @param {Function} [onFocusOut] - action to preform when you focus out of codemirror.
|
||||||
|
* @param {string} [helpText] - helper text.
|
||||||
|
* @param {object} [options] - option object that overrides codemirror default options such as the styling.
|
||||||
|
*/
|
||||||
|
|
||||||
const JSON_EDITOR_DEFAULTS = {
|
const JSON_EDITOR_DEFAULTS = {
|
||||||
// IMPORTANT: `gutters` must come before `lint` since the presence of
|
// IMPORTANT: `gutters` must come before `lint` since the presence of
|
||||||
|
@ -13,20 +30,16 @@ const JSON_EDITOR_DEFAULTS = {
|
||||||
showCursorWhenSelecting: true,
|
showCursorWhenSelecting: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Component.extend({
|
export default class JsonEditorComponent extends Component {
|
||||||
showToolbar: true,
|
value = null;
|
||||||
title: null,
|
valueUpdated = null;
|
||||||
subTitle: null,
|
onFocusOut = null;
|
||||||
helpText: null,
|
readOnly = false;
|
||||||
value: null,
|
options = null;
|
||||||
options: null,
|
|
||||||
valueUpdated: null,
|
|
||||||
onFocusOut: null,
|
|
||||||
readOnly: false,
|
|
||||||
|
|
||||||
init() {
|
constructor() {
|
||||||
this._super(...arguments);
|
super(...arguments);
|
||||||
this.options = { ...JSON_EDITOR_DEFAULTS, ...this.options };
|
this.options = { ...JSON_EDITOR_DEFAULTS, ...this.args.options };
|
||||||
if (this.options.autoHeight) {
|
if (this.options.autoHeight) {
|
||||||
this.options.viewportMargin = Infinity;
|
this.options.viewportMargin = Infinity;
|
||||||
delete this.options.autoHeight;
|
delete this.options.autoHeight;
|
||||||
|
@ -36,18 +49,23 @@ export default Component.extend({
|
||||||
this.options.lineNumbers = false;
|
this.options.lineNumbers = false;
|
||||||
delete this.options.gutters;
|
delete this.options.gutters;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
actions: {
|
get getShowToolbar() {
|
||||||
updateValue(...args) {
|
return this.args.showToolbar === false ? false : true;
|
||||||
if (this.valueUpdated) {
|
}
|
||||||
this.valueUpdated(...args);
|
|
||||||
}
|
@action
|
||||||
},
|
updateValue(...args) {
|
||||||
onFocus(...args) {
|
if (this.args.valueUpdated) {
|
||||||
if (this.onFocusOut) {
|
this.args.valueUpdated(...args);
|
||||||
this.onFocusOut(...args);
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
@action
|
||||||
});
|
onFocus(...args) {
|
||||||
|
if (this.args.onFocusOut) {
|
||||||
|
this.args.onFocusOut(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import BackendCrumbMixin from 'vault/mixins/backend-crumb';
|
||||||
|
|
||||||
|
export default class DiffController extends Controller.extend(BackendCrumbMixin) {}
|
|
@ -103,6 +103,7 @@ Router.map(function() {
|
||||||
|
|
||||||
this.route('list', { path: '/list/*secret' });
|
this.route('list', { path: '/list/*secret' });
|
||||||
this.route('show', { path: '/show/*secret' });
|
this.route('show', { path: '/show/*secret' });
|
||||||
|
this.route('diff', { path: '/diff/*id' });
|
||||||
this.route('metadata', { path: '/metadata/*secret' });
|
this.route('metadata', { path: '/metadata/*secret' });
|
||||||
this.route('edit-metadata', { path: '/edit-metadata/*secret' });
|
this.route('edit-metadata', { path: '/edit-metadata/*secret' });
|
||||||
this.route('create', { path: '/create/*secret' });
|
this.route('create', { path: '/create/*secret' });
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
export default class diff extends Route {
|
||||||
|
@service store;
|
||||||
|
|
||||||
|
beforeModel() {
|
||||||
|
let { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
let { id } = params;
|
||||||
|
return this.store.queryRecord('secret-v2', {
|
||||||
|
backend: this.backend,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.set('backend', this.backend); // for backendCrumb
|
||||||
|
controller.set('id', model.id); // for navigation on tabs
|
||||||
|
controller.set('model', model);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
.visual-diff {
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
color: $ui-gray-010;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-deleted .jsondiffpatch-property-name,
|
||||||
|
.jsondiffpatch-deleted pre,
|
||||||
|
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
|
||||||
|
.jsondiffpatch-textdiff-deleted {
|
||||||
|
background: $red-500;
|
||||||
|
}
|
||||||
|
.jsondiffpatch-added .jsondiffpatch-property-name,
|
||||||
|
.jsondiffpatch-added .jsondiffpatch-value pre,
|
||||||
|
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
|
||||||
|
.jsondiffpatch-textdiff-added {
|
||||||
|
background: $green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsondiffpatch-property-name {
|
||||||
|
color: $ui-gray-300;
|
||||||
|
}
|
|
@ -118,3 +118,14 @@
|
||||||
margin: 0 $spacing-xs;
|
margin: 0 $spacing-xs;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-diff-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: $spacing-s;
|
||||||
|
|
||||||
|
.diff-status {
|
||||||
|
display: flex;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
@import './components/confirm';
|
@import './components/confirm';
|
||||||
@import './components/console-ui-panel';
|
@import './components/console-ui-panel';
|
||||||
@import './components/control-group';
|
@import './components/control-group';
|
||||||
|
@import './components/diff-version-selector';
|
||||||
@import './components/doc-link';
|
@import './components/doc-link';
|
||||||
@import './components/empty-state';
|
@import './components/empty-state';
|
||||||
@import './components/env-banner';
|
@import './components/env-banner';
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<Toolbar>
|
||||||
|
<div class="version-diff-toolbar" data-test-version-diff-toolbar>
|
||||||
|
{{!-- Left side version --}}
|
||||||
|
<BasicDropdown
|
||||||
|
@class="popup-menu"
|
||||||
|
@horizontalPosition="auto-right"
|
||||||
|
@verticalPosition="below"
|
||||||
|
as |D|
|
||||||
|
>
|
||||||
|
<D.trigger
|
||||||
|
data-test-popup-menu-trigger="left-version"
|
||||||
|
@class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||||
|
@tagName="button"
|
||||||
|
>
|
||||||
|
Version {{or this.leftSideVersionSelected this.args.model.currentVersion}}
|
||||||
|
<Chevron @direction="down" @isButton={{true}} />
|
||||||
|
</D.trigger>
|
||||||
|
<D.content @class="popup-menu-content">
|
||||||
|
<nav class="box menu">
|
||||||
|
<ul class="menu-list">
|
||||||
|
{{#each (reverse this.args.model.versions) as |leftSideSecretVersion|}}
|
||||||
|
<li class="action" data-test-leftSide-version={{leftSideSecretVersion.version}}>
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
{{on "click" (fn this.selectVersion leftSideSecretVersion.version D.actions "left")}}
|
||||||
|
>
|
||||||
|
Version {{leftSideSecretVersion.version}}
|
||||||
|
{{#if (and (eq leftSideSecretVersion.version (or this.leftSideVersionSelected this.args.model.currentVersion)) (not leftSideSecretVersion.destroyed) (not leftSideSecretVersion.deleted))}}
|
||||||
|
<Icon @glyph="check-circle-outline" class="has-text-success is-pulled-right" />
|
||||||
|
{{else if leftSideSecretVersion.destroyed}}
|
||||||
|
<Icon @glyph="cancel-square-fill" class="has-text-danger is-pulled-right" />
|
||||||
|
{{else if leftSideSecretVersion.deleted}}
|
||||||
|
<Icon @glyph="cancel-square-fill" class="has-text-grey is-pulled-right" />
|
||||||
|
{{/if}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</D.content>
|
||||||
|
</BasicDropdown>
|
||||||
|
{{!-- Right side version --}}
|
||||||
|
<BasicDropdown
|
||||||
|
@class="popup-menu"
|
||||||
|
@horizontalPosition="right"
|
||||||
|
@verticalPosition="below"
|
||||||
|
as |D|
|
||||||
|
>
|
||||||
|
<D.trigger
|
||||||
|
@class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||||
|
@tagName="button"
|
||||||
|
data-test-popup-menu-trigger="right-version"
|
||||||
|
>
|
||||||
|
Version {{or this.rightSideVersionSelected this.rightSideVersionInit}}
|
||||||
|
<Chevron @direction="down" @isButton={{true}} />
|
||||||
|
</D.trigger>
|
||||||
|
<D.content @class="popup-menu-content">
|
||||||
|
<nav class="box menu">
|
||||||
|
<ul class="menu-list">
|
||||||
|
{{#each (reverse this.args.model.versions) as |rightSideSecretVersion|}}
|
||||||
|
<li class="action">
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
{{on "click" (fn this.selectVersion rightSideSecretVersion.version D.actions "right")}}
|
||||||
|
data-test-rightSide-version={{rightSideSecretVersion.version}}
|
||||||
|
>
|
||||||
|
Version {{rightSideSecretVersion.version}}
|
||||||
|
{{#if (and (eq rightSideSecretVersion.version (or this.rightSideVersionSelected this.rightSideVersionInit)) (not rightSideSecretVersion.destroyed) (not rightSideSecretVersion.deleted))}}
|
||||||
|
<Icon @glyph="check-circle-outline" class="has-text-success is-pulled-right" />
|
||||||
|
{{else if rightSideSecretVersion.destroyed}}
|
||||||
|
<Icon @glyph="cancel-square-fill" class="has-text-danger is-pulled-right" />
|
||||||
|
{{else if rightSideSecretVersion.deleted}}
|
||||||
|
<Icon @glyph="cancel-square-fill" class="has-text-grey is-pulled-right" />
|
||||||
|
{{/if}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</D.content>
|
||||||
|
</BasicDropdown>
|
||||||
|
{{!-- Status --}}
|
||||||
|
{{#if this.statesMatch}}
|
||||||
|
<div class="diff-status">
|
||||||
|
<span>States match</span>
|
||||||
|
<Icon @glyph="check-circle-fill" class="has-text-success" />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
|
||||||
|
<div class="form-section visual-diff">
|
||||||
|
<pre>{{{this.visualDiff}}}</pre>
|
||||||
|
</div>
|
|
@ -1,32 +1,36 @@
|
||||||
{{#if showToolbar }}
|
<div ...attributes>
|
||||||
<div data-test-component="json-editor-toolbar">
|
{{#if this.getShowToolbar }}
|
||||||
<Toolbar>
|
<div data-test-component="json-editor-toolbar">
|
||||||
<label class="is-label" data-test-component="json-editor-title">
|
<Toolbar>
|
||||||
{{title}}
|
<label class="is-label" data-test-component="json-editor-title">
|
||||||
{{#if subTitle }}
|
{{@title}}
|
||||||
<span class="is-size-9 is-lowercase has-text-grey">({{ subTitle }})</span>
|
{{#if @subTitle }}
|
||||||
{{/if}}
|
<span class="is-size-9 is-lowercase has-text-grey">({{ @subTitle }})</span>
|
||||||
</label>
|
{{/if}}
|
||||||
<ToolbarActions>
|
</label>
|
||||||
{{yield}}
|
<ToolbarActions>
|
||||||
<div class="toolbar-separator"></div>
|
{{yield}}
|
||||||
<CopyButton class="button is-transparent" @clipboardText={{value}}
|
<div class="toolbar-separator"></div>
|
||||||
@buttonType="button" @success={{action (set-flash-message 'Data copied!')}}>
|
<CopyButton class="button is-transparent" @clipboardText={{@value}}
|
||||||
<Icon @glyph="copy-action" aria-label="Copy" />
|
@buttonType="button" @success={{action (set-flash-message 'Data copied!')}}>
|
||||||
</CopyButton>
|
<Icon @glyph="copy-action" aria-label="Copy" />
|
||||||
</ToolbarActions>
|
</CopyButton>
|
||||||
</Toolbar>
|
</ToolbarActions>
|
||||||
</div>
|
</Toolbar>
|
||||||
{{/if}}
|
</div>
|
||||||
{{ivy-codemirror
|
{{/if}}
|
||||||
data-test-component="json-editor"
|
|
||||||
value=value
|
{{ivy-codemirror
|
||||||
options=options
|
data-test-component="json-editor"
|
||||||
valueUpdated=(action "updateValue")
|
value=@value
|
||||||
onFocusOut=(action "onFocus")
|
options=this.options
|
||||||
}}
|
valueUpdated=(action "updateValue")
|
||||||
{{#if helpText }}
|
onFocusOut=(action "onFocus")
|
||||||
<div class="box is-shadowless is-fullwidth has-short-padding">
|
}}
|
||||||
<p class="sub-text">{{ helpText }}</p>
|
|
||||||
</div>
|
{{#if @helpText }}
|
||||||
{{/if}}
|
<div class="box is-shadowless is-fullwidth has-short-padding">
|
||||||
|
<p class="sub-text">{{ @helpText }}</p>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
|
@ -43,6 +43,20 @@
|
||||||
View version history
|
View version history
|
||||||
</SecretLink>
|
</SecretLink>
|
||||||
</li>
|
</li>
|
||||||
|
{{#if (gt @model.versions.length 1)}}
|
||||||
|
<li class="action">
|
||||||
|
<li>
|
||||||
|
<LinkTo
|
||||||
|
class="link"
|
||||||
|
@route="vault.cluster.secrets.backend.diff"
|
||||||
|
@model={{@model.id}}
|
||||||
|
data-test-view-diff
|
||||||
|
>
|
||||||
|
View diff
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</D.content>
|
</D.content>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<PageHeader as |p|>
|
||||||
|
<p.top>
|
||||||
|
<KeyValueHeader
|
||||||
|
@baseKey={{hash id=@model.id}}
|
||||||
|
@path="vault.cluster.secrets.backend.show"
|
||||||
|
@mode="show"
|
||||||
|
@showCurrent={{true}}
|
||||||
|
@root={{backendCrumb}}
|
||||||
|
/>
|
||||||
|
</p.top>
|
||||||
|
<p.levelLeft>
|
||||||
|
<h1 class="title is-3">
|
||||||
|
View diff
|
||||||
|
</h1>
|
||||||
|
</p.levelLeft>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<DiffVersionSelector
|
||||||
|
@model={{@model}}
|
||||||
|
/>
|
|
@ -71,6 +71,8 @@ module.exports = function(defaults) {
|
||||||
app.import('node_modules/codemirror/addon/lint/lint.js');
|
app.import('node_modules/codemirror/addon/lint/lint.js');
|
||||||
app.import('node_modules/codemirror/addon/lint/json-lint.js');
|
app.import('node_modules/codemirror/addon/lint/json-lint.js');
|
||||||
app.import('node_modules/text-encoder-lite/text-encoder-lite.js');
|
app.import('node_modules/text-encoder-lite/text-encoder-lite.js');
|
||||||
|
app.import('node_modules/jsondiffpatch/dist/jsondiffpatch.umd.js');
|
||||||
|
app.import('node_modules/jsondiffpatch/dist/formatters-styles/html.css');
|
||||||
|
|
||||||
app.import('app/styles/bulma/bulma-radio-checkbox.css');
|
app.import('app/styles/bulma/bulma-radio-checkbox.css');
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
"filesize": "^4.2.1",
|
"filesize": "^4.2.1",
|
||||||
"flat": "^4.1.0",
|
"flat": "^4.1.0",
|
||||||
"ivy-codemirror": "IvyApp/ivy-codemirror#fb09333c5144da47e14a9e6260f80577d5408374",
|
"ivy-codemirror": "IvyApp/ivy-codemirror#fb09333c5144da47e14a9e6260f80577d5408374",
|
||||||
|
"jsondiffpatch": "^0.4.1",
|
||||||
"jsonlint": "^1.6.3",
|
"jsonlint": "^1.6.3",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"node-forge": "^0.10.0",
|
"node-forge": "^0.10.0",
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { click, settled, fillIn } from '@ember/test-helpers';
|
||||||
|
import { create } from 'ember-cli-page-object';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
|
import editPage from 'vault/tests/pages/secrets/backend/kv/edit-secret';
|
||||||
|
import listPage from 'vault/tests/pages/secrets/backend/list';
|
||||||
|
import apiStub from 'vault/tests/helpers/noop-all-api-requests';
|
||||||
|
import authPage from 'vault/tests/pages/auth';
|
||||||
|
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||||
|
|
||||||
|
const consoleComponent = create(consoleClass);
|
||||||
|
|
||||||
|
module('Acceptance | kv2 diff view', function(hooks) {
|
||||||
|
setupApplicationTest(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(async function() {
|
||||||
|
this.server = apiStub({ usePassthrough: true });
|
||||||
|
return authPage.login();
|
||||||
|
});
|
||||||
|
|
||||||
|
hooks.afterEach(function() {
|
||||||
|
this.server.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it shows correct diff status based on versions', async function(assert) {
|
||||||
|
const secretPath = `my-secret`;
|
||||||
|
|
||||||
|
await consoleComponent.runCommands([
|
||||||
|
`write sys/mounts/secret type=kv options=version=2`,
|
||||||
|
// delete any kv previously written here so that tests can be re-run
|
||||||
|
`delete secret/metadata/${secretPath}`,
|
||||||
|
'write -field=client_token auth/token/create policies=kv-v2-degrade',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await listPage.visitRoot({ backend: 'secret' });
|
||||||
|
await settled();
|
||||||
|
await listPage.create();
|
||||||
|
await settled();
|
||||||
|
await editPage.createSecret(secretPath, 'version1', 'hello');
|
||||||
|
await settled();
|
||||||
|
await click('[data-test-popup-menu-trigger="version"]');
|
||||||
|
await settled();
|
||||||
|
assert.dom('[data-test-view-diff]').doesNotExist('does not show diff view with only one version');
|
||||||
|
// add another version
|
||||||
|
await click('[data-test-secret-edit="true"]');
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
let secondKey = document.querySelectorAll('[data-test-secret-key]')[1];
|
||||||
|
let secondValue = document.querySelectorAll('.masked-value')[1];
|
||||||
|
await fillIn(secondKey, 'version2');
|
||||||
|
await fillIn(secondValue, 'world!');
|
||||||
|
await click('[data-test-secret-save]');
|
||||||
|
await settled();
|
||||||
|
await click('[data-test-popup-menu-trigger="version"]');
|
||||||
|
await settled();
|
||||||
|
assert.dom('[data-test-view-diff]').exists('does show diff view with two versions');
|
||||||
|
|
||||||
|
await click('[data-test-view-diff]');
|
||||||
|
await settled();
|
||||||
|
let diffBetweenVersion2and1 = document.querySelector('.jsondiffpatch-added').innerText;
|
||||||
|
assert.equal(diffBetweenVersion2and1, 'version2"world!"', 'shows the correct added part');
|
||||||
|
|
||||||
|
await click('[data-test-popup-menu-trigger="right-version"]');
|
||||||
|
await settled();
|
||||||
|
await click('[data-test-rightSide-version="2"]');
|
||||||
|
await settled();
|
||||||
|
assert.dom('.diff-status').exists('shows States Match');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
import EmberObject from '@ember/object';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { render, click, settled } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
|
||||||
|
const VERSIONS = [
|
||||||
|
{
|
||||||
|
version: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
module('Integration | Component | diff-version-selector', function(hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test('it renders', async function(assert) {
|
||||||
|
this.set(
|
||||||
|
'model',
|
||||||
|
EmberObject.create({
|
||||||
|
currentVersion: 2,
|
||||||
|
versions: VERSIONS,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await render(hbs`<DiffVersionSelector @model={{this.model}} />`);
|
||||||
|
let leftSideVersion = document
|
||||||
|
.querySelector('[data-test-popup-menu-trigger="left-version"]')
|
||||||
|
.innerText.trim();
|
||||||
|
assert.equal(leftSideVersion, 'Version 2', 'left side toolbar defaults to currentVersion');
|
||||||
|
|
||||||
|
await click('[data-test-popup-menu-trigger="left-version"]');
|
||||||
|
await settled();
|
||||||
|
assert.dom('[data-test-leftSide-version="1"]').exists('leftside shows both versions');
|
||||||
|
assert.dom('[data-test-leftSide-version="2"]').exists('leftside shows both versions');
|
||||||
|
});
|
||||||
|
});
|
13
ui/yarn.lock
13
ui/yarn.lock
|
@ -8540,6 +8540,11 @@ detect-port@^1.3.0:
|
||||||
address "^1.0.1"
|
address "^1.0.1"
|
||||||
debug "^2.6.0"
|
debug "^2.6.0"
|
||||||
|
|
||||||
|
diff-match-patch@^1.0.0:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
|
||||||
|
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
|
||||||
|
|
||||||
diff@^3.5.0:
|
diff@^3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||||
|
@ -13642,6 +13647,14 @@ json5@^2.1.2, json5@^2.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
|
jsondiffpatch@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz#9fb085036767f03534ebd46dcd841df6070c5773"
|
||||||
|
integrity sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==
|
||||||
|
dependencies:
|
||||||
|
chalk "^2.3.0"
|
||||||
|
diff-match-patch "^1.0.0"
|
||||||
|
|
||||||
jsonfile@^2.1.0:
|
jsonfile@^2.1.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
||||||
|
|
Loading…
Reference in New Issue