From 596b5aaf7ed572e60727942e69a118122b247692 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 15 Jul 2019 12:14:32 -0500 Subject: [PATCH] UI: Add copy button for client/allocation UUIDs (#5926) The button shows a success icon and tooltip on click, and resets after two seconds. --- CHANGELOG.md | 1 + ui/app/components/addon-copy-button.js | 2 + ui/app/components/copy-button.js | 16 +++ ui/app/styles/components.scss | 1 + ui/app/styles/components/copy-button.scss | 16 +++ ui/app/styles/components/tooltip.scss | 7 +- ui/app/styles/core/buttons.scss | 5 + .../allocations/allocation/index.hbs | 5 +- ui/app/templates/clients/client.hbs | 5 +- ui/app/templates/components/copy-button.hbs | 22 ++++ .../components/freestyle/sg-copy-button.hbs | 6 ++ ui/app/templates/freestyle.hbs | 4 + ui/ember-cli-build.js | 2 +- ui/package.json | 3 + .../components/copy-button-test.js | 45 ++++++++ ui/yarn.lock | 102 ++++++++++++++++++ 16 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 ui/app/components/addon-copy-button.js create mode 100644 ui/app/components/copy-button.js create mode 100644 ui/app/styles/components/copy-button.scss create mode 100644 ui/app/templates/components/copy-button.hbs create mode 100644 ui/app/templates/components/freestyle/sg-copy-button.hbs create mode 100644 ui/tests/integration/components/copy-button-test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0ba2286b8f..991feec77d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ IMPROVEMENTS: * driver/docker: Added logging defaults to use json-file log driver with log rotation [[GH-5846](https://github.com/hashicorp/nomad/pull/5846)] * metrics: Added namespace label as appropriate to metrics [[GH-5847](https://github.com/hashicorp/nomad/issues/5847)] * ui: Moved client status, draining, and eligibility fields into single state column [[GH-5789](https://github.com/hashicorp/nomad/pull/5789)] + * ui: Added buttons to copy client and allocation UUIDs [[GH-5926](https://github.com/hashicorp/nomad/pull/5926)] BUG FIXES: diff --git a/ui/app/components/addon-copy-button.js b/ui/app/components/addon-copy-button.js new file mode 100644 index 000000000000..56178c181fbd --- /dev/null +++ b/ui/app/components/addon-copy-button.js @@ -0,0 +1,2 @@ +// This lets us use copy-button to wrap ember-cli-clipboard’s component +export { default } from 'ember-cli-clipboard/components/copy-button'; diff --git a/ui/app/components/copy-button.js b/ui/app/components/copy-button.js new file mode 100644 index 000000000000..d8a891d8575a --- /dev/null +++ b/ui/app/components/copy-button.js @@ -0,0 +1,16 @@ +import Component from '@ember/component'; +import { task, timeout } from 'ember-concurrency'; + +export default Component.extend({ + classNames: ['copy-button'], + + clipboardText: null, + state: null, + + indicateSuccess: task(function*() { + this.set('state', 'success'); + + yield timeout(2000); + this.set('state', null); + }).restartable(), +}); diff --git a/ui/app/styles/components.scss b/ui/app/styles/components.scss index 031f1b886aba..aa74aea62362 100644 --- a/ui/app/styles/components.scss +++ b/ui/app/styles/components.scss @@ -2,6 +2,7 @@ @import './components/badge'; @import './components/boxed-section'; @import './components/codemirror'; +@import './components/copy-button'; @import './components/cli-window'; @import './components/dropdown'; @import './components/ember-power-select'; diff --git a/ui/app/styles/components/copy-button.scss b/ui/app/styles/components/copy-button.scss new file mode 100644 index 000000000000..163aaddb6e79 --- /dev/null +++ b/ui/app/styles/components/copy-button.scss @@ -0,0 +1,16 @@ +.copy-button { + padding-left: 0.5rem; + margin-bottom: 2px; + + .button { + color: inherit; + + &.is-static { + background-color: inherit; + } + + svg { + fill: currentColor; + } + } +} diff --git a/ui/app/styles/components/tooltip.scss b/ui/app/styles/components/tooltip.scss index 68842c133334..38bddb5e6aed 100644 --- a/ui/app/styles/components/tooltip.scss +++ b/ui/app/styles/components/tooltip.scss @@ -1,5 +1,6 @@ .tooltip { position: relative; + pointer-events: all; } .tooltip::after { @@ -45,12 +46,14 @@ transition: top 0.1s ease-in-out; } -.tooltip:hover::after { +.tooltip:hover::after, +.tooltip.always-active::after { opacity: 1; bottom: 120%; } -.tooltip:hover::before { +.tooltip:hover::before, +.tooltip.always-active::before { opacity: 1; top: -20%; } diff --git a/ui/app/styles/core/buttons.scss b/ui/app/styles/core/buttons.scss index 9a360f3c9797..bad307ee8c2e 100644 --- a/ui/app/styles/core/buttons.scss +++ b/ui/app/styles/core/buttons.scss @@ -37,6 +37,11 @@ $button-box-shadow-standard: 0 2px 0 0 rgba($grey, 0.2); } } + &.is-borderless { + border: 0; + box-shadow: none; + } + @each $name, $pair in $colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); diff --git a/ui/app/templates/allocations/allocation/index.hbs b/ui/app/templates/allocations/allocation/index.hbs index 280744b08bba..578ec2ab7e99 100644 --- a/ui/app/templates/allocations/allocation/index.hbs +++ b/ui/app/templates/allocations/allocation/index.hbs @@ -16,7 +16,10 @@

Allocation {{model.name}} {{model.clientStatus}} - {{model.id}} + + {{model.id}} + {{copy-button clipboardText=model.id}} + {{#if model.isRunning}} {{two-step-button data-test-stop diff --git a/ui/app/templates/clients/client.hbs b/ui/app/templates/clients/client.hbs index 7e87fee34b93..a7aff9372dd9 100644 --- a/ui/app/templates/clients/client.hbs +++ b/ui/app/templates/clients/client.hbs @@ -2,7 +2,10 @@

{{or model.name model.shortId}} - {{model.id}} + + {{model.id}} + {{copy-button clipboardText=model.id}} +

diff --git a/ui/app/templates/components/copy-button.hbs b/ui/app/templates/components/copy-button.hbs new file mode 100644 index 000000000000..344c77f35880 --- /dev/null +++ b/ui/app/templates/components/copy-button.hbs @@ -0,0 +1,22 @@ +{{#if (eq state 'success')}} +
+ + {{x-icon 'copy-success'}} + +
+{{else if (eq state 'error')}} +
+ + {{x-icon 'alert-triangle'}} + +
+{{else}} + {{#addon-copy-button + class='button is-borderless is-small' + clipboardText=clipboardText + success=(perform indicateSuccess) + error=(action (mut state) 'error') + }} + {{x-icon 'copy-action'}} + {{/addon-copy-button}} +{{/if}} \ No newline at end of file diff --git a/ui/app/templates/components/freestyle/sg-copy-button.hbs b/ui/app/templates/components/freestyle/sg-copy-button.hbs new file mode 100644 index 000000000000..f3af7cf96c83 --- /dev/null +++ b/ui/app/templates/components/freestyle/sg-copy-button.hbs @@ -0,0 +1,6 @@ +{{#freestyle-usage "copy-button" title="Copy Button"}} + + e8c898a0-794b-9063-7a7f-bf0c4a405f83 + {{copy-button clipboardText="e8c898a0-794b-9063-7a7f-bf0c4a405f83"}} + +{{/freestyle-usage}} \ No newline at end of file diff --git a/ui/app/templates/freestyle.hbs b/ui/app/templates/freestyle.hbs index a68982d1f3d2..e6ccfd6836eb 100644 --- a/ui/app/templates/freestyle.hbs +++ b/ui/app/templates/freestyle.hbs @@ -35,6 +35,10 @@ {{freestyle/sg-buttons}} {{/section.subsection}} + {{#section.subsection name="Copy Button"}} + {{freestyle/sg-copy-button}} + {{/section.subsection}} + {{#section.subsection name="Diff Viewer"}} {{freestyle/sg-diff-viewer}} {{/section.subsection}} diff --git a/ui/ember-cli-build.js b/ui/ember-cli-build.js index 6e2fb01c5961..cfa0fb996672 100644 --- a/ui/ember-cli-build.js +++ b/ui/ember-cli-build.js @@ -11,7 +11,7 @@ module.exports = function(defaults) { blacklist: isProd ? ['ember-freestyle'] : [], }, svg: { - paths: ['public/images/icons'], + paths: ['node_modules/@hashicorp/structure-icons/dist', 'public/images/icons'], optimize: { plugins: [{ removeViewBox: false }], }, diff --git a/ui/package.json b/ui/package.json index 9f7e9ee36e5a..aedf7edd58b8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,6 +29,7 @@ "@babel/plugin-proposal-object-rest-spread": "^7.4.3", "@ember/jquery": "^0.6.0", "@ember/optional-features": "^0.7.0", + "@hashicorp/structure-icons": "^1.3.0", "broccoli-asset-rev": "^3.0.0", "bulma": "0.6.1", "core-js": "^2.4.1", @@ -44,6 +45,7 @@ "ember-auto-import": "^1.2.21", "ember-cli": "~3.4.4", "ember-cli-babel": "^7.1.2", + "ember-cli-clipboard": "^0.13.0", "ember-cli-dependency-checker": "^3.0.0", "ember-cli-deprecation-workflow": "^1.0.1", "ember-cli-eslint": "^5.1.0", @@ -92,6 +94,7 @@ "lodash.intersection": "^4.4.0", "prettier": "^1.4.4", "query-string": "^5.0.0", + "qunit-dom": "^0.9.0", "sass": "^1.17.3" }, "engines": { diff --git a/ui/tests/integration/components/copy-button-test.js b/ui/tests/integration/components/copy-button-test.js new file mode 100644 index 000000000000..cdde40beaa38 --- /dev/null +++ b/ui/tests/integration/components/copy-button-test.js @@ -0,0 +1,45 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +import sinon from 'sinon'; + +import { triggerCopyError, triggerCopySuccess } from 'ember-cli-clipboard/test-support'; + +module('Integration | Component | copy-button', function(hooks) { + setupRenderingTest(hooks); + + test('it shows the copy icon by default', async function(assert) { + await render(hbs`{{copy-button class='copy-button'}}`); + + assert.dom('.copy-button .icon-is-copy-action').exists(); + }); + + test('it shows the success icon on success and resets afterward', async function(assert) { + const clock = sinon.useFakeTimers(); + + await render(hbs`{{copy-button class='copy-button'}}`); + + await click('.copy-button button'); + await triggerCopySuccess('.copy-button button'); + + assert.dom('.copy-button .icon-is-copy-success').exists(); + + clock.runAll(); + + assert.dom('.copy-button .icon-is-copy-success').doesNotExist(); + assert.dom('.copy-button .icon-is-copy-action').exists(); + + clock.restore(); + }); + + test('it shows the error icon on error', async function(assert) { + await render(hbs`{{copy-button class='copy-button'}}`); + + await click('.copy-button button'); + await triggerCopyError('.copy-button button'); + + assert.dom('.copy-button .icon-is-alert-triangle').exists(); + }); +}); diff --git a/ui/yarn.lock b/ui/yarn.lock index 8d682c2b4831..df23003fb179 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -766,6 +766,11 @@ dependencies: "@glimmer/util" "^0.38.1" +"@hashicorp/structure-icons@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.3.0.tgz#1c7c1cb43a1c1aa92b073a7aa7956495ae14c3e0" + integrity sha512-wTKpdaAPphEY2kg5QbQTSUlhqLTpBBR1+1dXp4LYTN0PtMSpetyDDDhcSyvKE8i4h2nwPJBRRfeFlE1snaHd7w== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1614,6 +1619,13 @@ babel-plugin-ember-modules-api-polyfill@^2.6.0, babel-plugin-ember-modules-api-p dependencies: ember-rfc176-data "^0.3.8" +babel-plugin-ember-modules-api-polyfill@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.9.0.tgz#8503e7b4192aeb336b00265e6235258ff6b754aa" + integrity sha512-c03h50291phJ2gQxo/aIOvFQE2c6glql1A7uagE3XbPXpKVAJOUxtVDjvWG6UAB6BC5ynsJfMWvY0w4TPRKIHQ== + dependencies: + ember-rfc176-data "^0.3.9" + babel-plugin-feature-flags@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/babel-plugin-feature-flags/-/babel-plugin-feature-flags-0.3.1.tgz#9c827cf9a4eb9a19f725ccb239e85cab02036fc1" @@ -3162,6 +3174,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +clipboard@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" + integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -3854,6 +3875,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -4117,6 +4143,33 @@ ember-cli-babel@^7.1.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.2.0, ember-cl ensure-posix-path "^1.0.2" semver "^5.5.0" +ember-cli-babel@^7.7.3: + version "7.8.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.8.0.tgz#e596500eca0f5a7c9aaee755f803d1542f578acf" + integrity sha512-xUBgJQ81fqd7k/KIiGU+pjpoXhrmmRf9pUrqLenNSU5N+yeNFT5a1+w0b+p1F7oBphfXVwuxApdZxrmAHOdA3Q== + dependencies: + "@babel/core" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.3.4" + "@babel/plugin-proposal-decorators" "^7.3.0" + "@babel/plugin-transform-modules-amd" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.2.0" + "@babel/polyfill" "^7.0.0" + "@babel/preset-env" "^7.0.0" + "@babel/runtime" "^7.2.0" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.3.0" + babel-plugin-ember-modules-api-polyfill "^2.9.0" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.1.2" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" + broccoli-source "^1.1.0" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.0" + ember-cli-version-checker "^2.1.2" + ensure-posix-path "^1.0.2" + semver "^5.5.0" + ember-cli-broccoli-sane-watcher@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.2.2.tgz#9bb1b04ddeb2c086aecd8693cbaeca1d88dc160c" @@ -4128,6 +4181,17 @@ ember-cli-broccoli-sane-watcher@^2.1.1: rsvp "^3.0.18" sane "^2.4.1" +ember-cli-clipboard@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/ember-cli-clipboard/-/ember-cli-clipboard-0.13.0.tgz#47d3de3aec09987409c162cbff36f966a2c138b7" + integrity sha512-AA2J5lliP/DXUFKnQ+r/D3e4xiN3ttlmN8W+8WfZg7K8VeOYlWpMyGcUjmuLa7inLUCMjLbtG6nXc20AQ5OjDg== + dependencies: + broccoli-funnel "^1.1.0" + clipboard "^2.0.0" + ember-cli-babel "^7.7.3" + ember-cli-htmlbars "^3.0.1" + fastboot-transform "^0.1.3" + ember-cli-dependency-checker@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ember-cli-dependency-checker/-/ember-cli-dependency-checker-3.1.0.tgz#b39c6b537a1457d77892edf5ddcfa025cd1401e2" @@ -4820,6 +4884,11 @@ ember-rfc176-data@^0.3.8: resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.8.tgz#d46bbef9a0d57c803217b258cfd2e90d8e191848" integrity sha512-SQup3iG7SDLZNuf7nMMx5BC5truO8AYKRi80gApeQ07NsbuXV4LH75i5eOaxF0i8l9+H1tzv34kGe6rEh0C1NQ== +ember-rfc176-data@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.9.tgz#44b6e051ead6c044ea87bd551f402e2cf89a7e3d" + integrity sha512-EiTo5YQS0Duy0xp9gCP8ekzv9vxirNi7MnIB4zWs+thtWp/mEKgf5mkiiLU2+oo8C5DuavVHhoPQDmyxh8Io1Q== + ember-router-generator@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" @@ -5443,6 +5512,14 @@ fast-sourcemap-concat@^1.4.0: source-map-url "^0.3.0" sourcemap-validator "^1.1.0" +fastboot-transform@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/fastboot-transform/-/fastboot-transform-0.1.3.tgz#7dea0b117594afd8772baa6c9b0919644e7f7dcd" + integrity sha512-6otygPIJw1ARp1jJb+6KVO56iKBjhO+5x59RSC9qiZTbZRrv+HZAuP00KD3s+nWMvcFDemtdkugki9DNFTTwCQ== + dependencies: + broccoli-stew "^1.5.0" + convert-source-map "^1.5.1" + faye-websocket@~0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -6005,6 +6082,13 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" @@ -9002,6 +9086,14 @@ quick-temp@^0.1.2, quick-temp@^0.1.3, quick-temp@^0.1.5, quick-temp@^0.1.8: rimraf "^2.5.4" underscore.string "~3.3.4" +qunit-dom@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/qunit-dom/-/qunit-dom-0.9.0.tgz#99d15fffbf06059e543bb93dae8fe0a3f42a27b9" + integrity sha512-MvVEoCcf8BHVPD3gXg5GBfNy3JMZ3U3yOha4MB1rFs698EpvxMprOfC+NMEGvOF9Epm6GrsA0BFOdCKHd8Orrw== + dependencies: + broccoli-funnel "^2.0.2" + broccoli-merge-trees "^3.0.1" + qunit@~2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/qunit/-/qunit-2.6.2.tgz#551210c5cf857258a4fe39a7fe15d9e14dfef22c" @@ -9621,6 +9713,11 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -10443,6 +10540,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tiny-lr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"