29 |
30 |
34 |
35 | 39 | Once you've created your Radar you can use this service 40 | to generate an interactive version of your Technology Radar. Not sure how? 41 | Read this first. 42 |
43 | 44 | Building your radar... 45 |Your Technology Radar will be available in just a few seconds
46 |57 | Enter the URL of your 58 | Google Sheet, CSV or JSON file below… 59 |
60 | 71 |75 | There are no blips on this quadrant, please check your Google sheet/CSV/JSON file once. 76 |
77 |
')
33 | }
34 | }
35 |
36 | module.exports = {
37 | renderBanner,
38 | }
39 |
--------------------------------------------------------------------------------
/src/stylesheets/_colors.scss:
--------------------------------------------------------------------------------
1 | $green: #86b782;
2 | $blue: #1ebccd;
3 | $orange: #f38a3e;
4 | $violet: #b32059;
5 | $pink: #ee0b77;
6 |
7 | $grey-darkest: #bababa;
8 | $grey-dark: #cacaca;
9 | $grey: #dadada;
10 | $grey-light: #eee;
11 | $grey-lightest: #fafafa;
12 | $grey-even-darker: #d7d7d7;
13 |
14 | $white: #fff;
15 | $black: #000;
16 | $grey-text: rgb(51, 51, 51);
17 |
18 | $grey-alpha-03: rgba(255, 255, 255, 0.3);
19 |
20 | // Brand colors for use across theme.
21 | $black: #000000; //onyx
22 | $white: #ffffff; //white
23 | $wave: #163c4d; //wave
24 | $wave-light: #6b8591; //wave-light
25 | $flamingo: #e16a7c; //flamingo
26 | $mist: #edf1f3; //mist (ededed)
27 | $sapphire: #47a1ad; //$sapphire
28 | $jade: #6b9e78; //jade
29 | $turmeric: #cc850a; //turmeric
30 | $amethyst: #634f7d; //amethyst
31 |
32 | // Other colors
33 | $mist-s30: #71777d;
34 | $mist-s20: #d5d9db;
35 | $mist-s10: #e1e5e7;
36 | $mist-light: #f7fafc;
37 | $flamingo-s40: #bd4257;
38 |
39 | $button-normal: $wave; //button color
40 | $button-disabled: #909090; //disabled state for links and buttons
41 |
42 | $link-normal: $black; //links color
43 | $link-hover: #9b293c; //links hover state color
44 | $error-text: #d14234;
45 |
46 | $flamingo-dark: #9b293c; //flamingo-dark
47 | $sapphire-dark: #1f8290; //$sapphire-dark
48 | $jade-dark: #517b5c; //jade-dark
49 | $turmeric-dark: #a06908; //turmeric-dark
50 |
--------------------------------------------------------------------------------
/src/graphing/components/alternativeRadars.js:
--------------------------------------------------------------------------------
1 | const d3 = require('d3')
2 | const { constructSheetUrl } = require('../../util/urlUtils')
3 |
4 | function renderAlternativeRadars(radarFooter, alternatives, currentSheet) {
5 | const alternativesContainer = radarFooter.append('div').classed('alternative-radars', true)
6 |
7 | for (let i = 0; alternatives.length > 0; i++) {
8 | const list = alternatives.splice(0, 5)
9 |
10 | const alternativesList = alternativesContainer
11 | .append('ul')
12 | .classed(`alternative-radars__list`, true)
13 | .classed(`alternative-radars__list__row-${i}`, true)
14 |
15 | list.forEach(function (alternative) {
16 | const alternativeListItem = alternativesList.append('li').classed('alternative-radars__list-item', true)
17 |
18 | alternativeListItem
19 | .append('a')
20 | .classed('alternative-radars__list-item-link', true)
21 | .attr('href', constructSheetUrl(alternative))
22 | .attr('role', 'tab')
23 | .text(alternative)
24 |
25 | if (currentSheet === alternative) {
26 | alternativeListItem.classed('active', true)
27 |
28 | d3.selectAll('.alternative-radars__list-item a').attr('aria-selected', null)
29 | alternativeListItem.select('a').attr('aria-selected', 'true')
30 | }
31 | })
32 | }
33 | }
34 |
35 | module.exports = {
36 | renderAlternativeRadars,
37 | }
38 |
--------------------------------------------------------------------------------
/src/images/third-quadrant-btn-bg.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/fourth-quadrant-btn-bg.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/second-quadrant-btn-bg.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/spec/graphing/config-spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | getScale,
3 | getGraphSize,
4 | getScaledQuadrantWidth,
5 | getScaledQuadrantHeightWithGap,
6 | isValidConfig,
7 | } = require('../../src/graphing/config')
8 | describe('Graphing Config', () => {
9 | it('should get the scale size for different window size', () => {
10 | window.innerWidth = 1440
11 | expect(getScale()).toStrictEqual(1.25)
12 |
13 | window.innerWidth = 1880
14 | expect(getScale()).toStrictEqual(1.5)
15 | })
16 |
17 | it('should get the graph size', () => {
18 | expect(getGraphSize()).toStrictEqual(1056)
19 | })
20 |
21 | it('should get the scaled quadrant width', () => {
22 | expect(getScaledQuadrantWidth(1.25)).toStrictEqual(640)
23 | })
24 |
25 | it('should get the scaled quadrant height with gap', () => {
26 | expect(getScaledQuadrantHeightWithGap(1.25)).toStrictEqual(680)
27 | })
28 |
29 | it('should validate the configs for quadrants and rings', () => {
30 | const oldEnv = process.env
31 | expect(isValidConfig()).toBeTruthy()
32 |
33 | process.env.QUADRANTS = '["radar"]'
34 | expect(isValidConfig()).toBeFalsy()
35 |
36 | process.env.QUADRANTS = '["radar", "r", "ra", "rad", "rada"]'
37 | expect(isValidConfig()).toBeFalsy()
38 |
39 | process.env.RINGS = '[]'
40 | expect(isValidConfig()).toBeFalsy()
41 |
42 | process.env.RINGS = '["radar", "r", "ra", "rad", "rada"]'
43 | expect(isValidConfig()).toBeFalsy()
44 |
45 | process.env = oldEnv
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/docker-existing-dockerfile
3 | {
4 | "name": "Existing Dockerfile",
5 | // Sets the run context to one level up instead of the .devcontainer folder.
6 | "context": "..",
7 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
8 | "dockerFile": "../Dockerfile",
9 | // Set *default* container specific settings.json values on container create.
10 | "settings": {},
11 | // Add the IDs of extensions you want installed when the container is created.
12 | "extensions": []
13 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
14 | // "forwardPorts": [],
15 | // Uncomment the next line to run commands after the container is created - for example installing curl.
16 | // "postCreateCommand": "apt-get update && apt-get install -y curl",
17 | // Uncomment when using a ptrace-based debugger like C++, Go, and Rust
18 | // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
19 | // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
20 | // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
21 | // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
22 | // "remoteUser": "vscode"
23 | }
24 |
--------------------------------------------------------------------------------
/src/stylesheets/_buttons.scss:
--------------------------------------------------------------------------------
1 | @import 'colors';
2 | @import 'layout';
3 |
4 | @if $UIRefresh2022 {
5 | .buttons {
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | justify-content: center;
10 | margin: 64px auto 56px;
11 |
12 | @include layout-margin(calc(3 / 4), $screen-small);
13 | @include layout-margin(calc(6 / 8), $screen-medium);
14 | @include layout-margin(calc(8 / 12), $screen-large);
15 | @include layout-margin(calc(6 / 12), $screen-xlarge);
16 | @include layout-margin(calc(6 / 12), $screen-xxlarge);
17 | @include layout-margin(calc(4 / 12), $screen-xxxlarge);
18 |
19 | @include media-query-medium {
20 | flex-direction: row;
21 | }
22 |
23 | button {
24 | border: none;
25 | font-size: 20px;
26 | color: $white;
27 | width: 220px;
28 | height: 48px;
29 | margin: 0 15px 32px;
30 | cursor: pointer;
31 |
32 | @include media-query-medium {
33 | margin: 0 15px;
34 | }
35 |
36 | a {
37 | border: none;
38 | color: $white;
39 |
40 | &:hover {
41 | color: $white;
42 | }
43 | }
44 | }
45 |
46 | &__wave-btn {
47 | background-color: $wave;
48 | }
49 |
50 | &__flamingo-btn {
51 | background-color: $flamingo-s40;
52 | color: $white;
53 |
54 | display: inline-flex;
55 | align-items: center;
56 | justify-content: center;
57 |
58 | width: 220px;
59 | height: 48px;
60 |
61 | border: none;
62 | font-size: 20px;
63 |
64 | &:hover {
65 | color: $white;
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/stylesheets/_error.scss:
--------------------------------------------------------------------------------
1 | @import 'colors';
2 | @import 'layout';
3 |
4 | .error-container {
5 | text-align: center;
6 | padding-top: 48px;
7 |
8 | .error-container__message {
9 | @if not $UIRefresh2022 {
10 | width: 60%;
11 | display: inline-block;
12 | }
13 | @if $UIRefresh2022 {
14 | p:first-child {
15 | color: $error-text;
16 | }
17 | p {
18 | margin: 0;
19 | }
20 | }
21 | }
22 |
23 | .error-title {
24 | font-weight: 400;
25 | }
26 |
27 | .error-subtitle {
28 | margin-top: -10px;
29 | }
30 |
31 | .switch-account-button {
32 | margin: 0 0 35px 0;
33 | }
34 |
35 | .switch-account-button-newui {
36 | background-color: $button-normal;
37 | color: $white;
38 | font-size: 20px;
39 | font-family: $baseFontFamily;
40 | line-height: 24px;
41 | font-weight: bold;
42 | padding-left: 30px;
43 | padding-right: 30px;
44 | margin-top: 10px;
45 | margin-bottom: 18px;
46 | border: none;
47 | text-transform: none;
48 | cursor: pointer;
49 | &:focus {
50 | outline: auto;
51 | }
52 | }
53 | }
54 |
55 | .input-sheet {
56 | .page-not-found {
57 | font-size: 40px;
58 | font-weight: 900;
59 | margin-bottom: 20px;
60 | }
61 |
62 | .message p {
63 | font-size: 25px;
64 | }
65 | }
66 |
67 | .support {
68 | margin-top: 20px;
69 |
70 | p {
71 | font-weight: 500;
72 | font-size: 30px;
73 | margin-bottom: 1px;
74 | }
75 | }
76 |
77 | .support-link {
78 | font-size: 26px;
79 | padding: 20px;
80 |
81 | span {
82 | padding: 0 40px;
83 | display: table-cell;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/images/search-logo-2x.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/stylesheets/_landingpage.scss:
--------------------------------------------------------------------------------
1 | @import 'layout';
2 | @import 'colors';
3 | @import 'fonts';
4 |
5 | .helper-description {
6 | @include eight-column-layout-margin;
7 | padding-top: 32px;
8 | margin-top: 24px;
9 |
10 | p {
11 | font-weight: bold;
12 | margin: 0;
13 | }
14 |
15 | .loader-text {
16 | display: none;
17 | text-align: center;
18 | &__title {
19 | display: inline-block;
20 | font-size: 2.5rem;
21 | font-weight: bold;
22 | margin-bottom: 1rem;
23 | }
24 | }
25 | }
26 |
27 | .input-sheet-form {
28 | @include eight-column-layout-margin;
29 | display: flex;
30 | flex-direction: column;
31 |
32 | p {
33 | margin-top: 88px;
34 | text-align: center;
35 | }
36 |
37 | p.with-error {
38 | margin-top: 48px;
39 | }
40 |
41 | form {
42 | display: flex;
43 | flex-direction: column;
44 | justify-content: center;
45 | flex-wrap: nowrap;
46 | align-items: center;
47 |
48 | input#document-input {
49 | font-family: $baseFontFamily;
50 | background-color: $mist;
51 | border: 1px solid #d5d9db;
52 | letter-spacing: 0.06px;
53 | height: 48px;
54 | margin-bottom: 20px;
55 | color: $black;
56 | font-size: 18px;
57 |
58 | &::placeholder {
59 | color: #3c606f;
60 | }
61 | }
62 |
63 | input[type='submit'] {
64 | background-color: $button-normal;
65 | color: $white;
66 | font-size: 20px;
67 | font-family: $baseFontFamily;
68 | line-height: 24px;
69 | font-weight: bold;
70 | padding-left: 30px;
71 | padding-right: 30px;
72 | margin-bottom: 18px;
73 | border: none;
74 | cursor: pointer;
75 | &:focus {
76 | outline: auto;
77 | }
78 | }
79 | input:disabled {
80 | cursor: not-allowed;
81 | opacity: 0.9;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/stylesheets/_form.scss:
--------------------------------------------------------------------------------
1 | .input-sheet {
2 | margin: 60px auto;
3 |
4 | p {
5 | font-weight: 100;
6 | color: $black;
7 | font-size: 18px;
8 | }
9 |
10 | .input-sheet__logo {
11 | text-align: center;
12 | padding-bottom: 40px;
13 | a {
14 | border-bottom: none;
15 |
16 | img {
17 | width: 200px;
18 | }
19 | }
20 | }
21 |
22 | .radar-footer {
23 | padding-top: 200px;
24 | }
25 |
26 | .input-sheet__banner {
27 | background-image: url('/images/tech-radar-landing-page-wide.png');
28 | background-repeat: no-repeat;
29 | background-color: $grey-light;
30 | background-size: cover;
31 | background-position: center;
32 | width: 100%;
33 | margin: 0 auto;
34 | text-align: center;
35 | min-height: 285px;
36 | display: table;
37 |
38 | div {
39 | display: table-cell;
40 | vertical-align: middle;
41 | }
42 | p,
43 | h1 {
44 | color: $white;
45 | }
46 |
47 | a {
48 | color: $white;
49 | border-bottom-color: $white;
50 | }
51 | }
52 |
53 | .input-sheet__form {
54 | width: 50%;
55 | margin: 0 auto;
56 | text-align: center;
57 | padding-top: 30px;
58 |
59 | button {
60 | font-family: inherit;
61 | border: none;
62 | background-color: transparent;
63 | margin: 0;
64 | padding: 0;
65 | }
66 | }
67 |
68 | input[type='text'] {
69 | border-bottom: 2px solid $grey-even-darker;
70 | display: block;
71 | font-size: 18px;
72 | margin-bottom: 30px;
73 | padding: 10px;
74 | transition:
75 | box-shadow 0.3s,
76 | border 0.3s;
77 |
78 | &:focus,
79 | &.focus {
80 | outline: none;
81 | border-bottom: 2px solid $grey-even-darker;
82 | box-shadow: none;
83 | }
84 | }
85 |
86 | form,
87 | input,
88 | a {
89 | font-family: inherit;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/graphing/config.js:
--------------------------------------------------------------------------------
1 | const quadrantSize = 512
2 | const quadrantGap = 32
3 |
4 | const getQuadrants = () => {
5 | return JSON.parse(process.env.QUADRANTS || null) || ['Techniques', 'Platforms', 'Tools', 'Languages & Frameworks']
6 | }
7 |
8 | const getRings = () => {
9 | return JSON.parse(process.env.RINGS || null) || ['Adopt', 'Trial', 'Assess', 'Hold']
10 | }
11 |
12 | const isBetween = (number, startNumber, endNumber) => {
13 | return startNumber <= number && number <= endNumber
14 | }
15 | const isValidConfig = () => {
16 | return getQuadrants().length === 4 && isBetween(getRings().length, 1, 4)
17 | }
18 |
19 | const graphConfig = {
20 | effectiveQuadrantHeight: quadrantSize + quadrantGap / 2,
21 | effectiveQuadrantWidth: quadrantSize + quadrantGap / 2,
22 | quadrantHeight: quadrantSize,
23 | quadrantWidth: quadrantSize,
24 | quadrantsGap: quadrantGap,
25 | minBlipWidth: 12,
26 | blipWidth: 22,
27 | groupBlipHeight: 24,
28 | newGroupBlipWidth: 88,
29 | existingGroupBlipWidth: 124,
30 | rings: getRings(),
31 | quadrants: getQuadrants(),
32 | groupBlipAngles: [30, 35, 60, 80],
33 | maxBlipsInRings: [8, 22, 17, 18],
34 | }
35 |
36 | const uiConfig = {
37 | subnavHeight: 60,
38 | bannerHeight: 200,
39 | tabletBannerHeight: 300,
40 | headerHeight: 80,
41 | legendsHeight: 42,
42 | tabletViewWidth: 1280,
43 | mobileViewWidth: 768,
44 | }
45 |
46 | function getScale() {
47 | return window.innerWidth < 1800 ? 1.25 : 1.5
48 | }
49 |
50 | function getGraphSize() {
51 | return graphConfig.effectiveQuadrantHeight + graphConfig.effectiveQuadrantWidth
52 | }
53 |
54 | function getScaledQuadrantWidth(scale) {
55 | return graphConfig.quadrantWidth * scale
56 | }
57 |
58 | function getScaledQuadrantHeightWithGap(scale) {
59 | return (graphConfig.quadrantHeight + graphConfig.quadrantsGap) * scale
60 | }
61 |
62 | module.exports = {
63 | graphConfig,
64 | uiConfig,
65 | getScale,
66 | getGraphSize,
67 | getScaledQuadrantWidth,
68 | getScaledQuadrantHeightWithGap,
69 | isValidConfig,
70 | }
71 |
--------------------------------------------------------------------------------
/src/models/blip.js:
--------------------------------------------------------------------------------
1 | const { graphConfig } = require('../graphing/config')
2 | const IDEAL_BLIP_WIDTH = 22
3 | const Blip = function (name, ring, isNew, status, topic, description) {
4 | let self, blipText, isGroup, id, groupIdInGraph
5 |
6 | self = {}
7 | isGroup = false
8 |
9 | self.width = IDEAL_BLIP_WIDTH
10 |
11 | self.name = function () {
12 | return name
13 | }
14 |
15 | self.id = function () {
16 | return id || -1
17 | }
18 |
19 | self.groupBlipWidth = function () {
20 | return isNew ? graphConfig.newGroupBlipWidth : graphConfig.existingGroupBlipWidth
21 | }
22 |
23 | self.topic = function () {
24 | return topic || ''
25 | }
26 |
27 | self.description = function () {
28 | return description || ''
29 | }
30 |
31 | self.isNew = function () {
32 | if (status) {
33 | return status.toLowerCase() === 'new'
34 | }
35 |
36 | return isNew
37 | }
38 |
39 | self.hasMovedIn = function () {
40 | return status.toLowerCase() === 'moved in'
41 | }
42 |
43 | self.hasMovedOut = function () {
44 | return status.toLowerCase() === 'moved out'
45 | }
46 |
47 | self.hasNoChange = function () {
48 | return status.toLowerCase() === 'no change'
49 | }
50 |
51 | self.status = function () {
52 | return status.toLowerCase() || ''
53 | }
54 |
55 | self.isGroup = function () {
56 | return isGroup
57 | }
58 |
59 | self.groupIdInGraph = function () {
60 | return groupIdInGraph || ''
61 | }
62 |
63 | self.setGroupIdInGraph = function (groupId) {
64 | groupIdInGraph = groupId
65 | }
66 |
67 | self.ring = function () {
68 | return ring
69 | }
70 |
71 | self.blipText = function () {
72 | return blipText || ''
73 | }
74 |
75 | self.setBlipText = function (newBlipText) {
76 | blipText = newBlipText
77 | }
78 |
79 | self.setId = function (newId) {
80 | id = newId
81 | }
82 |
83 | self.setIsGroup = function (isAGroupBlip) {
84 | isGroup = isAGroupBlip
85 | }
86 |
87 | return self
88 | }
89 |
90 | module.exports = Blip
91 |
--------------------------------------------------------------------------------
/src/stylesheets/_herobanner.scss:
--------------------------------------------------------------------------------
1 | @import 'colors';
2 | @import 'layout';
3 |
4 | @if $UIRefresh2022 {
5 | .hero-banner {
6 | height: $tabletBannerHeight;
7 | background-color: $amethyst;
8 | background-image: url('/images/banner-image-mobile.jpg');
9 | background-size: contain;
10 | background-repeat: no-repeat;
11 | background-position: right;
12 | color: $white;
13 | display: flex;
14 | align-items: center;
15 | margin: 0 !important;
16 |
17 | @include media-query-large {
18 | background-image: url('/images/banner-image-mobile.jpg');
19 | }
20 |
21 | @include media-query-xlarge {
22 | background-image: url('/images/banner-image-desktop.jpg');
23 | height: $bannerHeight;
24 | background-size: cover;
25 | }
26 |
27 | &__wrapper {
28 | margin: auto;
29 | width: 90%;
30 |
31 | @include layout-margin(1, $screen-small);
32 | @include layout-margin(1, $screen-medium);
33 | @include layout-margin(1, $screen-large);
34 | @include layout-margin(1, $screen-xlarge);
35 | @include layout-margin(1, $screen-xxlarge);
36 | @include layout-margin(1, $screen-xxxlarge);
37 | }
38 |
39 | &__title-text {
40 | width: fit-content;
41 | text-align: left;
42 | text-transform: none;
43 | letter-spacing: 0;
44 | cursor: pointer;
45 | }
46 |
47 | &__subtitle-text {
48 | font-weight: 630;
49 | width: 50%;
50 | text-align: left;
51 | text-transform: none;
52 | letter-spacing: 0;
53 | overflow: hidden;
54 | text-overflow: ellipsis;
55 | display: -webkit-box;
56 | line-height: 1.75rem;
57 | -webkit-line-clamp: 4;
58 | -webkit-box-orient: vertical;
59 |
60 | @include media-query-medium {
61 | width: 37.5%;
62 | -webkit-line-clamp: 2;
63 | }
64 |
65 | @include media-query-large {
66 | width: 25%;
67 | }
68 |
69 | @include media-query-xlarge {
70 | width: 33%;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | setup: true
4 |
5 | orbs:
6 | continuation: circleci/continuation@0.2.0
7 |
8 | executors:
9 | base:
10 | docker:
11 | - image: cimg/node:18.12.1
12 | user: root
13 |
14 | commands:
15 | install-node-packages:
16 | description: Install node packages
17 | steps:
18 | - restore_cache:
19 | key: node-cache-v2-{{ checksum "package-lock.json" }}
20 | - run:
21 | name: Install node packages
22 | command: npm install
23 | - save_cache:
24 | paths:
25 | - ./node_modules
26 | key: node-cache-v2-{{ checksum "package-lock.json" }}
27 |
28 | jobs:
29 | tests:
30 | executor: base
31 | steps:
32 | - checkout
33 | - install-node-packages
34 | - run:
35 | name: Run linter and unit tests with coverage
36 | command: npm run quality
37 | check-for-pr:
38 | executor: base
39 | steps:
40 | - checkout
41 | - run: |
42 | if [[ $CIRCLE_PULL_REQUEST ]]; then
43 | circleci-agent step halt
44 | fi
45 | - continuation/continue:
46 | configuration_path: .circleci/deployment-workflow.yml
47 | docker-push:
48 | executor: base
49 | steps:
50 | - checkout
51 | - setup_remote_docker:
52 | version: 20.10.12
53 | docker_layer_caching: true
54 | - run:
55 | name: Build and push Docker image to DockerHub
56 | command: ./docker_push.sh
57 |
58 | workflows:
59 | main:
60 | jobs:
61 | - tests
62 | - check-for-pr:
63 | requires:
64 | - tests
65 | - approve-docker-push:
66 | type: approval
67 | filters:
68 | tags:
69 | only: /^v.*/
70 | branches:
71 | ignore: /.*/
72 | - docker-push:
73 | requires:
74 | - approve-docker-push
75 | filters:
76 | tags:
77 | only: /^v.*/
78 | branches:
79 | ignore: /.*/
80 |
--------------------------------------------------------------------------------
/spec/util/contentValidator-spec.js:
--------------------------------------------------------------------------------
1 | const ContentValidator = require('../../src/util/contentValidator')
2 | const MalformedDataError = require('../../src/exceptions/malformedDataError')
3 | const ExceptionMessages = require('../../src/util/exceptionMessages')
4 |
5 | describe('ContentValidator', function () {
6 | describe('verifyContent', function () {
7 | it('does not return anything if content is valid', function () {
8 | var columnNames = ['name', 'ring', 'quadrant', 'isNew', 'description']
9 | var contentValidator = new ContentValidator(columnNames)
10 |
11 | expect(contentValidator.verifyContent()).not.toBeDefined()
12 | })
13 |
14 | it('raises an error if content is empty', function () {
15 | var columnNames = []
16 | var contentValidator = new ContentValidator(columnNames)
17 |
18 | expect(function () {
19 | contentValidator.verifyContent()
20 | }).toThrow(new MalformedDataError(ExceptionMessages.MISSING_CONTENT))
21 | })
22 | })
23 |
24 | describe('verifyHeaders', function () {
25 | it('raises an error if one of the headers is empty', function () {
26 | var columnNames = ['ring', 'quadrant', 'isNew', 'description']
27 | var contentValidator = new ContentValidator(columnNames)
28 |
29 | expect(function () {
30 | contentValidator.verifyHeaders()
31 | }).toThrow(new MalformedDataError(ExceptionMessages.MISSING_HEADERS))
32 | })
33 |
34 | it('does not return anything if the all required headers are present', function () {
35 | var columnNames = ['name', 'ring', 'quadrant', 'isNew', 'description']
36 | var contentValidator = new ContentValidator(columnNames)
37 |
38 | expect(contentValidator.verifyHeaders()).not.toBeDefined()
39 | })
40 |
41 | it('does not care about white spaces in the headers', function () {
42 | var columnNames = [' name', 'ring ', ' quadrant', 'isNew ', ' description ']
43 | var contentValidator = new ContentValidator(columnNames)
44 |
45 | expect(contentValidator.verifyHeaders()).not.toBeDefined()
46 | })
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/src/util/autoComplete.js:
--------------------------------------------------------------------------------
1 | const $ = require('jquery')
2 | require('jquery-ui/ui/widgets/autocomplete')
3 |
4 | const config = require('../config')
5 | const featureToggles = config().featureToggles
6 |
7 | $.widget('custom.radarcomplete', $.ui.autocomplete, {
8 | _create: function () {
9 | this._super()
10 | this.widget().menu('option', 'items', '> :not(.ui-autocomplete-quadrant)')
11 | },
12 | _renderMenu: function (ul, items) {
13 | let currentQuadrant = ''
14 |
15 | items.forEach((item) => {
16 | const quadrantName = item.quadrant.quadrant.name()
17 | if (quadrantName !== currentQuadrant) {
18 | ul.append(`