From 5ea748005c015de7ae6205862988d6fb222938f4 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Wed, 12 Sep 2018 20:23:39 +0100 Subject: [PATCH] UI: External Source markers (#4640) 1. Addition of external source icons for services marked as such. 2. New %with-tooltip css component (wip) 3. New 'no healthcheck' icon as external sources might not have healthchecks, also minus icon on node cards in the service detail view 4. If a service doesn't have healthchecks, we use the [Services] tabs as the default instead of the [Health Checks] tab in the Service detail page. 5. `css-var` helper. The idea here is that it will eventually be replaced with pure css custom properties instead of having to use JS. It would be nice to be able to build the css variables into the JS at build time (you'd probably still want to specify in config which variables you wanted available in JS), but that's possible future work. Lastly there is probably a tiny bit more testing edits here than usual, I noticed that there was an area where the dynamic mocking wasn't happening, it was just using the mocks from consul-api-double, the mocks I was 'dynamically' setting happened to be the same as the ones in consul-api-double. I've fixed this here also but it wasn't effecting anything until actually made certain values dynamic. --- ui-v2/app/controllers/dc/nodes/show.js | 7 ++- ui-v2/app/controllers/dc/services/index.js | 10 ++-- ui-v2/app/helpers/css-var.js | 12 +++++ ui-v2/app/helpers/service/external-source.js | 16 ++++++ ui-v2/app/models/service.js | 2 + ui-v2/app/styles/app.scss | 1 + ui-v2/app/styles/base/icons/index.scss | 3 ++ ui-v2/app/styles/base/index.scss | 1 + .../healthchecked-resource/layout.scss | 11 ++-- .../healthchecked-resource/skin.scss | 24 ++++++--- ui-v2/app/styles/components/icons/index.scss | 42 ++++++++++++--- .../styles/components/list-collection.scss | 1 + .../styles/components/product/app-view.scss | 3 ++ ui-v2/app/styles/components/table.scss | 6 +++ .../styles/components/tabular-collection.scss | 12 ++++- ui-v2/app/styles/components/with-tooltip.scss | 9 ++++ .../styles/components/with-tooltip/index.scss | 2 + .../components/with-tooltip/layout.scss | 39 ++++++++++++++ .../styles/components/with-tooltip/skin.scss | 9 ++++ ui-v2/app/styles/core/typography.scss | 4 +- .../components/healthchecked-resource.hbs | 9 ++-- .../app/templates/dc/nodes/-healthchecks.hbs | 7 ++- ui-v2/app/templates/dc/nodes/-services.hbs | 5 +- ui-v2/app/templates/dc/nodes/show.hbs | 13 +++-- ui-v2/app/templates/dc/services/index.hbs | 14 +++-- ui-v2/app/templates/dc/services/show.hbs | 7 +-- .../components/catalog-filter.feature | 18 +++++-- .../acceptance/dc/nodes/services.feature | 16 ------ .../acceptance/dc/nodes/services/list.feature | 53 +++++++++++++++++++ .../dc/nodes/sessions/invalidate.feature | 2 +- .../acceptance/dc/nodes/sessions/list.feature | 2 +- ui-v2/tests/acceptance/dc/nodes/show.feature | 27 +++++++--- .../acceptance/dc/services/index.feature | 31 +++++++++-- .../tests/acceptance/dc/services/show.feature | 16 ++++++ .../list-steps.js} | 20 +++---- ui-v2/tests/helpers/type-to-url.js | 2 +- .../tests/integration/helpers/css-var-test.js | 32 +++++++++++ .../helpers/service/external-source-test.js | 32 +++++++++++ ui-v2/tests/pages/dc/nodes/show.js | 1 + ui-v2/tests/pages/dc/services/index.js | 1 + ui-v2/tests/pages/dc/services/show.js | 1 + ui-v2/tests/steps.js | 13 ++++- ui-v2/yarn.lock | 36 ++++++------- 43 files changed, 467 insertions(+), 105 deletions(-) create mode 100644 ui-v2/app/helpers/css-var.js create mode 100644 ui-v2/app/helpers/service/external-source.js create mode 100644 ui-v2/app/styles/base/icons/index.scss create mode 100644 ui-v2/app/styles/components/with-tooltip.scss create mode 100644 ui-v2/app/styles/components/with-tooltip/index.scss create mode 100644 ui-v2/app/styles/components/with-tooltip/layout.scss create mode 100644 ui-v2/app/styles/components/with-tooltip/skin.scss delete mode 100644 ui-v2/tests/acceptance/dc/nodes/services.feature create mode 100644 ui-v2/tests/acceptance/dc/nodes/services/list.feature rename ui-v2/tests/acceptance/steps/dc/nodes/{services-steps.js => services/list-steps.js} (85%) create mode 100644 ui-v2/tests/integration/helpers/css-var-test.js create mode 100644 ui-v2/tests/integration/helpers/service/external-source-test.js diff --git a/ui-v2/app/controllers/dc/nodes/show.js b/ui-v2/app/controllers/dc/nodes/show.js index 80c4c32870..b93c00f00d 100644 --- a/ui-v2/app/controllers/dc/nodes/show.js +++ b/ui-v2/app/controllers/dc/nodes/show.js @@ -15,7 +15,12 @@ export default Controller.extend(WithFiltering, { }, setProperties: function() { this._super(...arguments); - set(this, 'selectedTab', 'health-checks'); + // the default selected tab depends on whether you have any healthchecks or not + // so check the length here. + // This method is called immediately after `Route::setupController`, and done here rather than there + // as this is a variable used purely for view level things, if the view was different we might not + // need this variable + set(this, 'selectedTab', get(this.item, 'Checks.length') > 0 ? 'health-checks' : 'services'); }, filter: function(item, { s = '' }) { const term = s.toLowerCase(); diff --git a/ui-v2/app/controllers/dc/services/index.js b/ui-v2/app/controllers/dc/services/index.js index 373ca6f30d..a0cc0bd399 100644 --- a/ui-v2/app/controllers/dc/services/index.js +++ b/ui-v2/app/controllers/dc/services/index.js @@ -37,17 +37,17 @@ export default Controller.extend(WithHealthFiltering, { item.hasStatus(status) ); }, - totalWidth: computed('{maxPassing,maxWarning,maxCritical}', function() { + maxWidth: computed('{maxPassing,maxWarning,maxCritical}', function() { const PADDING = 32 * 3 + 13; return ['maxPassing', 'maxWarning', 'maxCritical'].reduce((prev, item) => { return prev + width(get(this, item)); }, PADDING); }), - thWidth: computed('totalWidth', function() { - return widthDeclaration(get(this, 'totalWidth')); + totalWidth: computed('maxWidth', function() { + return widthDeclaration(get(this, 'maxWidth')); }), - remainingWidth: computed('totalWidth', function() { - return htmlSafe(`width: calc(50% - ${Math.round(get(this, 'totalWidth') / 2)}px)`); + remainingWidth: computed('maxWidth', function() { + return htmlSafe(`width: calc(50% - ${Math.round(get(this, 'maxWidth') / 2)}px)`); }), maxPassing: computed('items', function() { return max(get(this, 'items'), 'ChecksPassing'); diff --git a/ui-v2/app/helpers/css-var.js b/ui-v2/app/helpers/css-var.js new file mode 100644 index 0000000000..13665f8cf9 --- /dev/null +++ b/ui-v2/app/helpers/css-var.js @@ -0,0 +1,12 @@ +import { helper } from '@ember/component/helper'; +const cssVars = { + '--kubernetes-color-svg': `url('data:image/svg+xml;charset=UTF-8,')`, + '--terraform-color-svg': `url('data:image/svg+xml;charset=UTF-8,')`, + '--nomad-color-svg': `url('data:image/svg+xml;charset=UTF-8,')`, + '--consul-color-svg': `url('data:image/svg+xml;charset=UTF-8,')`, +}; +export function cssVar(params, hash) { + return typeof cssVars[params[0]] !== 'undefined' ? cssVars[params[0]] : params[1]; +} + +export default helper(cssVar); diff --git a/ui-v2/app/helpers/service/external-source.js b/ui-v2/app/helpers/service/external-source.js new file mode 100644 index 0000000000..32cc4f3dd0 --- /dev/null +++ b/ui-v2/app/helpers/service/external-source.js @@ -0,0 +1,16 @@ +import { helper } from '@ember/component/helper'; +import { get } from '@ember/object'; + +export function serviceExternalSource(params, hash) { + let source = get(params[0], 'ExternalSources.firstObject'); + if (!source) { + source = get(params[0], 'Meta.external-source'); + } + const prefix = typeof hash.prefix === 'undefined' ? '' : hash.prefix; + if (source) { + return `${prefix}${source}`; + } + return; +} + +export default helper(serviceExternalSource); diff --git a/ui-v2/app/models/service.js b/ui-v2/app/models/service.js index b3e4d540d2..c8e71ae05d 100644 --- a/ui-v2/app/models/service.js +++ b/ui-v2/app/models/service.js @@ -14,6 +14,8 @@ export default Model.extend({ }, }), Kind: attr('string'), + ExternalSources: attr(), + Meta: attr(), Address: attr('string'), Port: attr('number'), EnableTagOverride: attr('boolean'), diff --git a/ui-v2/app/styles/app.scss b/ui-v2/app/styles/app.scss index f7eea00ae9..b8c1b2d9bd 100644 --- a/ui-v2/app/styles/app.scss +++ b/ui-v2/app/styles/app.scss @@ -29,6 +29,7 @@ @import 'components/confirmation-dialog'; @import 'components/feedback-dialog'; @import 'components/notice'; +@import 'components/with-tooltip'; @import 'core/typography'; @import 'core/layout'; diff --git a/ui-v2/app/styles/base/icons/index.scss b/ui-v2/app/styles/base/icons/index.scss new file mode 100644 index 0000000000..58d7695495 --- /dev/null +++ b/ui-v2/app/styles/base/icons/index.scss @@ -0,0 +1,3 @@ +$consul-color-svg: url('data:image/svg+xml;charset=UTF-8,'); +$nomad-color-svg: url('data:image/svg+xml;charset=UTF-8,'); +$terraform-color-svg: url('data:image/svg+xml;charset=UTF-8,'); diff --git a/ui-v2/app/styles/base/index.scss b/ui-v2/app/styles/base/index.scss index e48bb67d47..1f66f3803b 100644 --- a/ui-v2/app/styles/base/index.scss +++ b/ui-v2/app/styles/base/index.scss @@ -1,3 +1,4 @@ @import './decoration/index'; @import './color/index'; @import './typography/index'; +@import './icons/index'; diff --git a/ui-v2/app/styles/components/healthchecked-resource/layout.scss b/ui-v2/app/styles/components/healthchecked-resource/layout.scss index 630905bdda..d22fffc043 100644 --- a/ui-v2/app/styles/components/healthchecked-resource/layout.scss +++ b/ui-v2/app/styles/components/healthchecked-resource/layout.scss @@ -12,10 +12,6 @@ white-space: nowrap; } %healthchecked-resource, -%healthchecked-resource li { - border-color: $ui-gray-200; -} -%healthchecked-resource, %healthchecked-resource header, %healthchecked-resource li { position: relative; @@ -76,3 +72,10 @@ .healthy .healthchecked-resource li:only-child strong { display: none; } +%healthchecked-resource ul:empty { + position: absolute; + top: 18px; + right: 20px; + width: 1em; + height: 1em; +} diff --git a/ui-v2/app/styles/components/healthchecked-resource/skin.scss b/ui-v2/app/styles/components/healthchecked-resource/skin.scss index e800763170..0938e5f26f 100644 --- a/ui-v2/app/styles/components/healthchecked-resource/skin.scss +++ b/ui-v2/app/styles/components/healthchecked-resource/skin.scss @@ -1,3 +1,14 @@ +%healthchecked-resource { + border: $decor-border-100; + box-shadow: 0 4px 8px 0 rgba($ui-black, 0.05); +} +%healthchecked-resource li { + border-top: $decor-border-100; +} +%healthchecked-resource, +%healthchecked-resource li { + border-color: $ui-gray-200; +} %healthchecked-resource li.passing { color: $ui-color-success; } @@ -7,17 +18,16 @@ %healthchecked-resource li.critical { color: $ui-color-failure; } -%healthchecked-resource { - border: $decor-border-100; - box-shadow: 0 4px 8px 0 rgba($ui-black, 0.05); -} %healthchecked-resource:hover, %healthchecked-resource:focus { box-shadow: 0 8px 10px 0 rgba($ui-black, 0.1); } -%healthchecked-resource li { - border-top: $decor-border-100; -} %healthchecked-resource { border-radius: $radius-small; } +%healthchecked-resource ul:empty { + @extend %with-no-healthchecks; +} +%healthchecked-resource ul:empty::before { + color: $ui-gray-400; +} diff --git a/ui-v2/app/styles/components/icons/index.scss b/ui-v2/app/styles/components/icons/index.scss index 56699a270b..8d0f693a1c 100644 --- a/ui-v2/app/styles/components/icons/index.scss +++ b/ui-v2/app/styles/components/icons/index.scss @@ -1,15 +1,35 @@ -%pseudo-icon { - width: 1em; - height: 1em; - position: absolute; - top: 50%; - margin-top: -0.6em; +/*TODO: The old pseudo-icon was to specific */ +/* make a temporary one with the -- prefix */ +/* to make it more reusable temporarily */ +%--pseudo-icon { display: block; content: ''; + visibility: visible; + position: absolute; + top: 50%; background-repeat: no-repeat; background-position: center center; +} +%pseudo-icon-bg-img { + @extend %--pseudo-icon; + background-size: contain; + background-color: transparent; +} +%pseudo-icon-css { + @extend %--pseudo-icon; + width: 1em; + height: 1em; + margin-top: -0.6em; background-color: currentColor; - visibility: visible; +} +%pseudo-icon { + @extend %pseudo-icon-css; +} +%with-external-source-icon { + background-repeat: no-repeat; + background-size: contain; + width: 18px; + height: 18px; } %with-dot { content: ''; @@ -135,6 +155,10 @@ @extend %pseudo-icon; background-image: url('data:image/svg+xml;charset=UTF-8,'); } +%with-minus { + @extend %pseudo-icon; + background-image: url('data:image/svg+xml;charset=UTF-8,'); +} %with-warning-icon-orange { @extend %pseudo-icon; background-image: url('data:image/svg+xml;charset=UTF-8,'); @@ -183,3 +207,7 @@ @extend %with-cross; border-radius: 20%; } +%with-no-healthchecks::before { + @extend %with-minus; + border-radius: 20%; +} diff --git a/ui-v2/app/styles/components/list-collection.scss b/ui-v2/app/styles/components/list-collection.scss index 595a4a1c30..0b90ee9d90 100644 --- a/ui-v2/app/styles/components/list-collection.scss +++ b/ui-v2/app/styles/components/list-collection.scss @@ -8,6 +8,7 @@ } .healthy > div { width: calc(100% + 23px); + min-height: 500px; } .unhealthy > div { margin-bottom: 20px; diff --git a/ui-v2/app/styles/components/product/app-view.scss b/ui-v2/app/styles/components/product/app-view.scss index f1ad635b20..f8bfb7a6fb 100644 --- a/ui-v2/app/styles/components/product/app-view.scss +++ b/ui-v2/app/styles/components/product/app-view.scss @@ -30,6 +30,9 @@ display: flex; align-items: flex-start; } +%app-view h1 span { + @extend %with-external-source-icon; +} %app-view { margin-top: 50px; } diff --git a/ui-v2/app/styles/components/table.scss b/ui-v2/app/styles/components/table.scss index f6131fdf33..b7e9dc3183 100644 --- a/ui-v2/app/styles/components/table.scss +++ b/ui-v2/app/styles/components/table.scss @@ -11,6 +11,12 @@ td dt.warning { td dt.critical { @extend %with-critical; } +td span.zero { + @extend %with-no-healthchecks; + display: block; + text-indent: 20px; + color: $ui-gray-400; +} table:not(.sessions) tr { cursor: pointer; } diff --git a/ui-v2/app/styles/components/tabular-collection.scss b/ui-v2/app/styles/components/tabular-collection.scss index 6620da9c6c..3999fe20a0 100644 --- a/ui-v2/app/styles/components/tabular-collection.scss +++ b/ui-v2/app/styles/components/tabular-collection.scss @@ -16,6 +16,17 @@ table tr > * { tr > * dl { float: left; } +/* TODO: putting this here is less than ideal */ +/* but this is another area where I am specifically */ +/* targetting table-like things. This is now a prime */ +/* area for a bit of refactoring/reorganizing */ +html.template-service.template-list td:first-child a span, +html.template-node.template-show #services td:first-child a span { + @extend %with-external-source-icon; + float: left; + margin-right: 10px; + margin-top: 2px; +} html.template-service.template-list main table tr { @extend %services-row; } @@ -34,7 +45,6 @@ html.template-node.template-show main table tr { html.template-node.template-show main table.sessions tr { @extend %node-sessions-row; } - @media #{$--horizontal-session-list} { %node-sessions-row > * { // (100% / 7) - (300px / 6) - (120px / 6) diff --git a/ui-v2/app/styles/components/with-tooltip.scss b/ui-v2/app/styles/components/with-tooltip.scss new file mode 100644 index 0000000000..9ae6b79596 --- /dev/null +++ b/ui-v2/app/styles/components/with-tooltip.scss @@ -0,0 +1,9 @@ +@import './with-tooltip/index'; +%app-view h1 span { + @extend %with-tooltip; +} +%app-view h1 span { + text-indent: -9000px; + font-size: 0; + top: -9px; +} diff --git a/ui-v2/app/styles/components/with-tooltip/index.scss b/ui-v2/app/styles/components/with-tooltip/index.scss new file mode 100644 index 0000000000..bc18252196 --- /dev/null +++ b/ui-v2/app/styles/components/with-tooltip/index.scss @@ -0,0 +1,2 @@ +@import './skin'; +@import './layout'; diff --git a/ui-v2/app/styles/components/with-tooltip/layout.scss b/ui-v2/app/styles/components/with-tooltip/layout.scss new file mode 100644 index 0000000000..4bc6593075 --- /dev/null +++ b/ui-v2/app/styles/components/with-tooltip/layout.scss @@ -0,0 +1,39 @@ +%with-tooltip { + position: relative; + display: inline-flex; + justify-content: center; + align-items: center; +} +%with-tooltip::before, +%with-tooltip::after { + position: absolute; +} +%with-tooltip::before { + padding: 10px; + bottom: calc(100% + 5px); + text-align: center; + white-space: nowrap; + content: attr(data-tooltip); + // incase you are using text-indent to hide the + // text of the element %with-tooltip + text-indent: 0; +} +%with-tooltip::after { + content: ''; + left: 50%; + margin-left: -5px; + top: -10px; + width: 10px; + height: 10px; + transform: rotate(45deg); +} +%with-tooltip::after, +%with-tooltip::before { + display: none; +} +%with-tooltip:hover::after, +%with-tooltip:hover::before, +%with-tooltip:focus::after, +%with-tooltip:focus::before { + display: block; +} diff --git a/ui-v2/app/styles/components/with-tooltip/skin.scss b/ui-v2/app/styles/components/with-tooltip/skin.scss new file mode 100644 index 0000000000..1617d21d98 --- /dev/null +++ b/ui-v2/app/styles/components/with-tooltip/skin.scss @@ -0,0 +1,9 @@ +%with-tooltip::before, +%with-tooltip::after { + color: $ui-white; + background-color: $ui-gray-800; +} +%with-tooltip::before { + border-radius: $decor-radius-200; + box-shadow: 0 3px 1px 0 rgba($ui-black, 0.12); +} diff --git a/ui-v2/app/styles/core/typography.scss b/ui-v2/app/styles/core/typography.scss index 259db1cf41..3095fa1b78 100644 --- a/ui-v2/app/styles/core/typography.scss +++ b/ui-v2/app/styles/core/typography.scss @@ -48,7 +48,8 @@ td a { th, %breadcrumbs a, %action-group a, -%tab-nav { +%tab-nav, +%with-tooltip::before { font-weight: $typo-weight-medium; } main label a[rel*='help'], @@ -86,6 +87,7 @@ td { font-size: $typo-size-600; } th, +%with-tooltip::before, %healthchecked-resource strong, %footer { font-size: $typo-size-700; diff --git a/ui-v2/app/templates/components/healthchecked-resource.hbs b/ui-v2/app/templates/components/healthchecked-resource.hbs index 4b3f9bcbf9..cbf3f961f9 100644 --- a/ui-v2/app/templates/components/healthchecked-resource.hbs +++ b/ui-v2/app/templates/components/healthchecked-resource.hbs @@ -1,12 +1,12 @@ -
+
{{address}} {{name}} {{service}}
-