UNCLASSIFIED

Commit 58aeacc6 authored by hunter.congress's avatar hunter.congress
Browse files

Merge branch 'BULL-1182' of...

Merge branch 'BULL-1182' of https://repo1.dso.mil/platform-one/party-bus/launchboard/launchboard-fe into BULL-1182
parents cabf5d49 1d884303
...@@ -34,22 +34,22 @@ appearance, race, religion, or sexual identity and orientation. ...@@ -34,22 +34,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment Examples of behavior that contributes to creating a positive environment
include: include:
* Using welcoming and inclusive language - Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences - Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism - Gracefully accepting constructive criticism
* Focusing on what is best for the community - Focusing on what is best for the community
* Showing empathy towards other community members - Showing empathy towards other community members
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or - The use of sexualized language or imagery and unwelcome sexual attention or
advances advances
* Trolling, insulting/derogatory comments, and personal or political attacks - Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or electronic - Publishing others' private information, such as a physical or electronic
address, without explicit permission address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Our Responsibilities ## Our Responsibilities
......
# launchboard
Front-end for https://launchboard.apps.dso.mil
(staging: https://launchboard.staging.dso.mil)
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
## Testing
### Unit Tests
```
npm run test:unit
```
### End-to-End Tests
Platform One is still working on a solution to run `npm run test:e2e-ci` against a deployed app instance outside of Keycloak/Authservice.
We can run e2e tests on the Launchboard Front End by mocking api requests in Cypress (see `tests/e2e/fixtures/users/`).
```bash
# run e2e tests locally with the cypress ui
npm run test:e2e
# run e2e tests locally headless (no cypress ui)
npm run test:e2e -- --headless
```
...@@ -2841,6 +2841,12 @@ ...@@ -2841,6 +2841,12 @@
"@types/yargs": "^13.0.0" "@types/yargs": "^13.0.0"
} }
}, },
"@leichtgewicht/ip-codec": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz",
"integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==",
"dev": true
},
"@mdi/font": { "@mdi/font": {
"version": "5.9.55", "version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz",
...@@ -4665,18 +4671,6 @@ ...@@ -4665,18 +4671,6 @@
} }
} }
}, },
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-errors": { "ajv-errors": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
...@@ -4937,12 +4931,6 @@ ...@@ -4937,12 +4931,6 @@
"integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
"dev": true "dev": true
}, },
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
"dev": true
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
...@@ -8455,16 +8443,6 @@ ...@@ -8455,16 +8443,6 @@
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
"dev": true "dev": true
}, },
"dns-packet": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
"dev": true,
"requires": {
"ip": "^1.1.0",
"safe-buffer": "^5.0.1"
}
},
"dns-txt": { "dns-txt": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
...@@ -10548,17 +10526,6 @@ ...@@ -10548,17 +10526,6 @@
"integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==",
"dev": true "dev": true
}, },
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-proxy-middleware": { "http-proxy-middleware": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz",
...@@ -11957,9 +11924,9 @@ ...@@ -11957,9 +11924,9 @@
}, },
"dependencies": { "dependencies": {
"ws": { "ws": {
"version": "7.5.3", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
"dev": true "dev": true
} }
} }
...@@ -11993,9 +11960,9 @@ ...@@ -11993,9 +11960,9 @@
} }
}, },
"ws": { "ws": {
"version": "7.5.3", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==" "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg=="
} }
} }
}, },
...@@ -12535,9 +12502,9 @@ ...@@ -12535,9 +12502,9 @@
"dev": true "dev": true
}, },
"ws": { "ws": {
"version": "7.5.3", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
"dev": true "dev": true
} }
} }
...@@ -14026,6 +13993,17 @@ ...@@ -14026,6 +13993,17 @@
"requires": { "requires": {
"dns-packet": "^1.3.1", "dns-packet": "^1.3.1",
"thunky": "^1.0.2" "thunky": "^1.0.2"
},
"dependencies": {
"dns-packet": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.3.0.tgz",
"integrity": "sha512-Nce7YLu6YCgWRvOmDBsJMo9M5/jV3lEZ5vUWnWXYmwURvPylHvq7nkDWhNmk1ZQoZZOP7oQh/S0lSxbisKOfHg==",
"dev": true,
"requires": {
"@leichtgewicht/ip-codec": "^2.0.1"
}
}
} }
}, },
"multicast-dns-service-types": { "multicast-dns-service-types": {
...@@ -14672,6 +14650,18 @@ ...@@ -14672,6 +14650,18 @@
"dev": true, "dev": true,
"requires": { "requires": {
"url-parse": "^1.4.3" "url-parse": "^1.4.3"
},
"dependencies": {
"url-parse": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
}
} }
}, },
"os-browserify": { "os-browserify": {
...@@ -14879,12 +14869,6 @@ ...@@ -14879,12 +14869,6 @@
"integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
"dev": true "dev": true
}, },
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"path-exists": { "path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
...@@ -18417,39 +18401,6 @@ ...@@ -18417,39 +18401,6 @@
} }
} }
}, },
"sockjs": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz",
"integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==",
"dev": true,
"requires": {
"faye-websocket": "^0.11.3",
"uuid": "^3.4.0",
"websocket-driver": "^0.7.4"
},
"dependencies": {
"faye-websocket": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
"integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
"dev": true,
"requires": {
"websocket-driver": ">=0.5.1"
}
},
"websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
"integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
"dev": true,
"requires": {
"http-parser-js": ">=0.5.1",
"safe-buffer": ">=5.1.0",
"websocket-extensions": ">=0.1.1"
}
}
}
},
"sockjs-client": { "sockjs-client": {
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
...@@ -18472,6 +18423,25 @@ ...@@ -18472,6 +18423,25 @@
"requires": { "requires": {
"ms": "^2.1.1" "ms": "^2.1.1"
} }
},
"faye-websocket": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
"integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
"dev": true,
"requires": {
"websocket-driver": ">=0.5.1"
}
},
"url-parse": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
} }
} }
}, },
...@@ -20664,16 +20634,6 @@ ...@@ -20664,16 +20634,6 @@
"schema-utils": "^2.5.0" "schema-utils": "^2.5.0"
} }
}, },
"url-parse": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"use": { "use": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
...@@ -21444,9 +21404,9 @@ ...@@ -21444,9 +21404,9 @@
"dev": true "dev": true
}, },
"ws": { "ws": {
"version": "7.5.3", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
"dev": true "dev": true
} }
} }
...@@ -21574,6 +21534,17 @@ ...@@ -21574,6 +21534,17 @@
"path-is-absolute": "^1.0.0", "path-is-absolute": "^1.0.0",
"readdirp": "^2.2.1", "readdirp": "^2.2.1",
"upath": "^1.1.1" "upath": "^1.1.1"
},
"dependencies": {
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
}
} }
}, },
"fsevents": { "fsevents": {
...@@ -21588,20 +21559,17 @@ ...@@ -21588,20 +21559,17 @@
} }
}, },
"glob-parent": { "glob-parent": {
"version": "3.1.0", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": { "requires": {
"is-glob": "^3.1.0", "is-glob": "^4.0.1"
"path-dirname": "^1.0.0"
}, },
"dependencies": { "dependencies": {
"is-glob": { "is-glob": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"requires": { "requires": {
"is-extglob": "^2.1.0" "is-extglob": "^2.1.0"
} }
...@@ -21618,6 +21586,25 @@ ...@@ -21618,6 +21586,25 @@
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"micromatch": "^3.1.10" "micromatch": "^3.1.10"
},
"dependencies": {
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}
} }
}, },
"is-absolute-url": { "is-absolute-url": {
...@@ -21655,6 +21642,20 @@ ...@@ -21655,6 +21642,20 @@
"ajv": "^6.1.0", "ajv": "^6.1.0",
"ajv-errors": "^1.0.0", "ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0" "ajv-keywords": "^3.1.0"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
}
} }
}, },
"semver": { "semver": {
...@@ -21663,6 +21664,17 @@ ...@@ -21663,6 +21664,17 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true "dev": true
}, },
"sockjs": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz",
"integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==",
"dev": true,
"requires": {
"faye-websocket": "^0.11.3",
"uuid": "^3.4.0",
"websocket-driver": "^0.7.4"
}
},
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
...@@ -21682,13 +21694,10 @@ ...@@ -21682,13 +21694,10 @@
} }
}, },
"ws": { "ws": {
"version": "6.2.2", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
"dev": true, "dev": true
"requires": {
"async-limiter": "~1.0.0"
}
} }
} }
}, },
......
import { HTTP } from "@/api/http-common";
export default {
async postMissionApp(missionApp) {
return await HTTP.post("/mission-app", missionApp);
},
};
...@@ -29,8 +29,7 @@ ...@@ -29,8 +29,7 @@
return-object return-object
single-line single-line
color="secondary" color="secondary"
style="width: 400px" class="select mx-auto"
class="mx-auto"
@change="updateTextArea()" @change="updateTextArea()"
></v-select> ></v-select>
...@@ -38,7 +37,6 @@ ...@@ -38,7 +37,6 @@
ref="inputRef" ref="inputRef"
clearable clearable
v-model="selectedMessage" v-model="selectedMessage"
style="width: 70%; font-size: 14px"
class="mx-auto mt-10" class="mx-auto mt-10"
placeholder="Edit your response in here or choose from the templated responses provided in the dropdown..." placeholder="Edit your response in here or choose from the templated responses provided in the dropdown..."
outlined outlined
...@@ -106,16 +104,17 @@ export default { ...@@ -106,16 +104,17 @@ export default {
async denyPendingRegistration() { async denyPendingRegistration() {
try { try {
const response = await TrainingService.putCoursePendingRegistrationsById( const response =
this.$props.courseId, await TrainingService.putCoursePendingRegistrationsById(
{ this.$props.courseId,
userId: this.$props.id, {
pmId: this.$props.pm, userId: this.$props.id,
mainText: this.mainText, pmId: this.$props.pm,
subText: this.subText, mainText: this.mainText,
add: false, subText: this.subText,
} add: false,
); }
);
this.total = response.meta?.total || 0; this.total = response.meta?.total || 0;
} catch (error) { } catch (error) {
const errorMessage = error; const errorMessage = error;
...@@ -128,3 +127,13 @@ export default { ...@@ -128,3 +127,13 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss" scoped>
.select {
width: 70%;
font-size: 14px;
}
.text-area {
width: 70%;
font-size: 14px;
}
</style>
<template>
<div :class="containerClass">
<div>
<h2 class="headers text-left" v-if="$vuetify.breakpoint.lgAndUp">
{{ header }}
</h2>
<h3 class="headers text-center mb-4" v-else>{{ header }}</h3>
{{ subheader }}
</div>
<v-btn class="mt-4" color="primary" @click.native="showMissionAppForm">
{{ btnText }}
</v-btn>
</div>
</template>
<script>
export default {
name: "GenerateMissionAppBanner",
data: () => ({
header: "Generate mission application",
subheader:
"Create your team’s Gitlab group, projects, code repositories, and Kubernetes Manifest Repository.",
btnText: "Start App Generation",
}),
methods: {
showMissionAppForm() {
this.$emit("show-mission-app-form", true);
},
},
computed: {
containerClass() {
return `${
this.$vuetify.breakpoint.lgAndUp
? "d-flex justify-space-between align-items-center container-margin-lg"
: "my-14"
} px-2 px-md-4 px-lg-8 px-xl-16 pb-0`;
},
},
};
</script>
<style lang="scss" scoped>
.container-margin-lg {
margin-top: 80px;
margin-bottom: 80px;
}
.headers {
padding: 0;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
</style>
<template>
<div>
<v-img
width="94.05px"
height="72px"
class="mx-auto my-3"
:src="img"
></v-img>
<h3 class="mb-3">{{ header }}</h3>
{{ subtext }}
</div>
</template>
<script>
import grogu from "@/assets/images/logos/Logo_P1_Yodahead-WH.png";
export default {
name: "GenerateMissionAppHeader",
data: () => ({
img: grogu,
header: "Generate Mission Application",
subtext:
"Fill in the following fields in order to generate your Platform One mission application",
}),
};
</script>
<template>
<v-form v-model="valid">
<v-sheet
color="tertiary"
class="d-flex mx-4 mx-lg-8 mx-xl-16 my-8 px-2 py-xl-14 py-lg-14 py-8"
>
<v-container fluid class="px-2 px-md-4 px-lg-8 px-xl-16 pb-0">
<v-row>
<v-col
cols="12"
xl="6"
lg="6"
class="d-flex flex-column pb-0 pb-lg-3 px-xl-16 px-lg-16"
>
<MissionAppGeneralInfo :missionApp="missionApp" />
<div class="sectionSpacer" />
<MissionAppRepo :missionApp="missionApp" />
</v-col>
<v-col
cols="12"
xl="6"
lg="6"
class="d-flex flex-column pb-0 pb-lg-3 px-xl-16 px-lg-16"
>
<MissionAppFeatures :missionApp="missionApp" />
<div class="sectionSpacer" />
<MissionAppDeployment :missionApp="missionApp" />
<div class="sectionSpacer" />
<MissionAppDns :missionApp="missionApp" />
<div class="sectionSpacer" />
</v-col>
</v-row>
<v-row>
<div class="missionAppButtons ml-auto mr-auto">
<div class="sectionSpacer" />
<v-btn color="secondary" @click="hideMissionAppForm" class="mr-2"
>Cancel</v-btn
>
<v-btn
color="primary"
:disabled="!valid"
@click="submitMissionAppForm"
class="ml-2"
>Generate Mission App</v-btn
>
</div>
</v-row>
</v-container>
</v-sheet>
</v-form>
</template>
<script>
import MissionAppGeneralInfo from "@/components/MissionAppGeneralInfo";
import MissionAppRepo from "@/components/MissionAppRepo";
import MissionAppFeatures from "@/components/MissionAppFeatures";
import MissionAppDeployment from "@/components/MissionAppDeployment";
import MissionAppDns from "@/components/MissionAppDns";
import MissionAppApi from "@/api/services/missionApp";
import { SET_ERROR_MESSAGE, SET_ERROR_DIALOG } from "@/store/mutation-types";
export default {
name: "GenerateMissionApplication",
components: {
MissionAppGeneralInfo,
MissionAppRepo,
MissionAppFeatures,
MissionAppDeployment,
MissionAppDns,
},
data: () => ({
missionApp: {
preload: false,
},
valid: false,
}),
methods: {
hideMissionAppForm() {
this.$emit("show-mission-app-form", false);
},
async submitMissionAppForm() {
console.log(JSON.stringify(this.missionApp));
try {
await MissionAppApi.postMissionApp(this.missionApp);
this.hideMissionAppForm();
} catch (e) {
this.$store.commit(SET_ERROR_MESSAGE, e);
this.$store.commit(SET_ERROR_DIALOG, true);
console.error("Unable to create mission app", e);
}
},
},
};
</script>
<style lang="scss" scoped>
.sectionSpacer {
margin-bottom: 20px;
margin-top: 20px;
}
</style>
<template>
<div>
<h4 class="text-xl-left text-lg-left ml-xl-0 ml-lg-0 pl-lg-0 pl-lg-0 mb-8">
Deployment
</h4>
<div class="smallSelect mx-xl-0 mx-lg-0 ml-auto mr-auto">
<v-select
v-model="missionApp.keycloak"
:items="keycloakOptions"
label="Auth. Keycloak Client"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
solo
/>
</div>
<div class="d-flex flex-column flex-xl-row flex-lg-row grow">
<div class="smallSelect mr-lg-4 ml-lg-0 ml-auto mr-auto">
<v-select
v-model="missionApp.stagingEnvironment"
:items="environments"
label="Staging Environment"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
solo
/>
</div>
<div class="smallSelect mx-xl-0 mx-lg-0 ml-auto mr-auto">
<v-select
v-model="missionApp.productionEnvironment"
:items="environments"
label="Production Environment"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
solo
/>
</div>
</div>
</div>
</template>
<script>
import inputRules from "@/utils/inputRules";
export default {
name: "MissionAppDeployment",
props: {
missionApp: {
type: Object,
required: true,
},
},
data: () => ({
inputRules,
keycloakOptions: ["Generic", "Custom"],
environments: ["IL2", "IL4", "IL5"],
}),
watch: {
missionApp() {
this.$emit("input", this.missionApp);
},
},
};
</script>
<style lang="scss" scoped>
.smallSelect {
width: 216px;
margin-bottom: 32px;
}
</style>
<template>
<div>
<h4 class="text-xl-left text-lg-left ml-xl-0 ml-lg-0 pl-lg-0 pl-lg-0 mb-8">
DNS
</h4>
<v-text-field
class="mb-8"
label="Staging URL"
v-model="missionApp.stagingUrl"
hint="* Required | eg: my-app-il2.staging.dso.mil"
persistent-hint
required
:rules="[inputRules.required]"
/>
<v-text-field
label="Production URL"
v-model="missionApp.productionUrl"
hint="* Required | eg: my-app.dso.mil"
persistent-hint
required
:rules="[inputRules.required]"
/>
</div>
</template>
<script>
import inputRules from "@/utils/inputRules";
export default {
name: "MissionAppDns",
props: {
missionApp: {
type: Object,
required: true,
},
},
data: () => ({
inputRules,
}),
watch: {
missionApp() {
this.$emit("input", this.missionApp);
},
},
};
</script>
<template>
<div>
<h4 class="text-xl-left text-lg-left ml-xl-0 ml-lg-0 pl-lg-0 pl-lg-0 mb-8">
Features
</h4>
<div
class="
d-flex
flex-column flex-xl-row flex-lg-row
justify-space-between
align-center
"
>
<div class="mt-n6 ml-xl-0 ml-lg-0 ml-n8">
<v-checkbox
v-model="persistance"
label="database (persistance)"
:ripple="false"
@change="filterPersistance"
/>
<div
class="persistantInputs ml-8"
v-for="(db, index) in persistantDbs"
:key="index"
>
<v-checkbox
class="my-n4"
:disabled="!persistance"
v-model="missionApp.features"
:label="db"
:ripple="false"
multiple
:value="db"
/>
</div>
</div>
<div class=".otherFeatures">
<div class="ml-n4" v-for="(f, index) in otherFeatures" :key="index">
<v-checkbox
v-model="missionApp.features"
:label="f.label"
:ripple="false"
multiple
:value="f.value"
/>
</div>
<v-row align="center">
<v-checkbox
v-model="hasAdditionalFeature"
hide-details
@change="removeAdditionalFeatures"
class="additionalFeatureSelect mt-0 shrink"
:ripple="false"
/>
<v-text-field
class="additionalFeatureInput"
v-model="additionalFeature"
:disabled="!hasAdditionalFeature"
label="Additional"
:value="missionApp.additionalFeatures"
@input="addAdditionalFeatures"
clearable
/>
</v-row>
</div>
</div>
</div>
</template>
<script>
export default {
name: "MissionAppFeatures",
props: {
missionApp: {
type: Object,
required: true,
},
},
data: () => ({
persistance: false,
persistantDbs: ["Postgresql", "mysql", "mongodb"],
otherFeatures: [
{
value: "S3",
label: "S3 (minion)",
},
{
value: "RabbitMQ",
label: "RabbitMQ (messaging)",
},
],
hasAdditionalFeature: false,
additionalFeature: "",
}),
methods: {
filterPersistance() {
if (!this.persistance) {
this.$set(
this.missionApp,
"features",
this.missionApp.features.filter(
(f) => !this.persistantDbs.includes(f)
)
);
}
},
addAdditionalFeatures() {
this.$set(this.missionApp, "additionalFeatures", this.additionalFeatures);
},
removeAdditionalFeatures() {
this.$set(
this.missionApp,
"additionalFeatures",
this.hasAdditionalFeature ? this.additionalFeatures : undefined
);
},
},
computed: {
additionalFeatures() {
return this.additionalFeature?.split(",").map((f) => f.trim());
},
},
watch: {
missionApp() {
this.$emit("input", this.missionApp);
},
},
};
</script>
<template>
<div class=".generalInfo">
<h4 class="text-xl-left text-lg-left ml-xl-0 ml-lg-0 pl-lg-0 pl-lg-0 mb-8">
General Info
</h4>
<v-text-field
class="mb-8"
label="Gitlab Root Folder"
v-model="missionApp.rootFolder"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
/>
<v-text-field
class="mb-8"
label="Product Team Name"
v-model="missionApp.productTeam"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
/>
<v-text-field
label="Product Name"
v-model="missionApp.product"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
/>
</div>
</template>
<script>
import inputRules from "@/utils/inputRules";
export default {
name: "MissionAppGeneralInfo",
data: () => ({
inputRules,
}),
props: {
missionApp: {
type: Object,
required: true,
},
},
watch: {
missionApp() {
this.$emit("input", this.missionApp);
},
},
};
</script>
<template>
<div class=".Repository">
<h4 class="text-xl-left text-lg-left ml-xl-0 ml-lg-0 pl-lg-0 pl-lg-0 mb-8">
Repository
</h4>
<v-text-field
class="mb-8"
label="Repository Name"
v-model="missionApp.repository"
hint="* Required"
persistent-hint
required
:rules="[inputRules.required]"
/>
<v-checkbox
class="mb-8"
v-model="missionApp.preload"
label="Preload with demo code (comes from hello world repos)"
:ripple="false"
/>
<div class="d-flex flex-column flex-xl-row flex-lg-row">
<v-select
v-model="missionApp.stack"
:items="stackTypes"
label="Code Stack"
hint="* Required"
persistent-hint
required
class="mr-2"
:rules="[inputRules.required]"
@change="resetPackageManager"
solo
/>
<v-select
v-model="missionApp.packageManager"
:disabled="!missionApp.stack"
:items="packageManagers.get(missionApp.stack)"
label="Package Manager"
hint="* Required"
persistent-hint
required
class="ml-2"
:rules="[inputRules.required]"
solo
/>
</div>
</div>
</template>
<script>
import inputRules from "@/utils/inputRules";
export default {
name: "MissionAppRepo",
data: () => ({
inputRules,
packageManagers: new Map([
["nodejs", ["yarn", "npm"]],
["python", ["pip", "pipenv"]],
["java", ["gradle", "maven"]],
["golang", ["default"]],
]),
}),
props: {
missionApp: {
type: Object,
required: true,
},
},
methods: {
resetPackageManager() {
delete this.missionApp.packageManager;
},
},
watch: {
missionApp() {
this.$emit("input", this.missionApp);
},
},
computed: {
stackTypes() {
return Array.from(this.packageManagers.keys());
},
},
};
</script>
<template> <template>
<v-container class="py-0"> <v-container class="py-0">
<v-list <v-list color="tertiary" max-height="300px" class="v-list pa-0">
color="tertiary" <v-list-item v-if="this.listItems.count === 0">
max-height="300px"
class="pa-0"
style="overflow-y: auto"
>
<v-list-item v-if="listItems.count === 0">
<v-list-item-content class="pa-0"> <v-list-item-content class="pa-0">
<v-list-item-title class="text-wrap text-left" <v-list-item-title class="text-wrap text-left"
>No notifications to display.</v-list-item-title >No notifications to display.</v-list-item-title
...@@ -28,7 +23,7 @@ ...@@ -28,7 +23,7 @@
v-text="item.subText" v-text="item.subText"
class="text-wrap mt-3" class="text-wrap mt-3"
></v-list-item-subtitle> ></v-list-item-subtitle>
<v-col style="width: 90%" class="text-right"> <v-col class="v-list-item-col text-right">
<v-btn <v-btn
color="primary" color="primary"
outlined outlined
...@@ -90,3 +85,11 @@ export default { ...@@ -90,3 +85,11 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss" scoped>
.v-list {
overflow-y: auto;
}
.v-list-item-col {
width: 90%;
}
</style>
...@@ -180,6 +180,7 @@ export default { ...@@ -180,6 +180,7 @@ export default {
this.total = response.meta?.total || 0; this.total = response.meta?.total || 0;
} catch (error) { } catch (error) {
const errorMessage = error; const errorMessage = error;
console.log("ERROR");
this.$store.commit(SET_ERROR_MESSAGE, errorMessage); this.$store.commit(SET_ERROR_MESSAGE, errorMessage);
this.$store.commit(SET_ERROR_DIALOG, true); this.$store.commit(SET_ERROR_DIALOG, true);
} }
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
<img v-if="item.avatar" :src="item.avatar" :alt="data.item.name" /> <img v-if="item.avatar" :src="item.avatar" :alt="data.item.name" />
<v-icon x-large v-else>mdi-account-circle</v-icon> <v-icon x-large v-else>mdi-account-circle</v-icon>
</v-list-item-avatar> </v-list-item-avatar>
<v-list-item-content> <v-list-item-content>
<v-list-item-title class="text-left">{{ item.name }}</v-list-item-title> <v-list-item-title class="text-left">{{ item.name }}</v-list-item-title>
<v-list-item-subtitle class="text-left">{{ <v-list-item-subtitle class="text-left">{{
item.email item.email
......
import MissionAppService from "@/api/services/missionApp";
import { HTTP } from "@/api/http-common";
const mockMissionAppBody = {
preload: true,
rootFolder: "root",
productTeam: "productTeam",
product: "product",
repository: "frontend",
stack: "nodejs",
packageManager: "npm",
features: ["mongodb"],
keycloak: "Generic",
stagingEnvironment: "IL2",
productionEnvironment: "IL2",
stagingUrl: "product.staging.dso.mil",
productionUrl: "product.dso.mil",
};
describe("mission app service", () => {
describe("should call HTTP", () => {
it("should postMissionApp", async () => {
HTTP.post = jest.fn().mockResolvedValue();
await MissionAppService.postMissionApp(mockMissionAppBody);
expect(HTTP.post).toHaveBeenCalledWith(
"/mission-app",
mockMissionAppBody
);
});
});
});
import Vuex from "vuex";
import Vuetify from "vuetify";
import Permission from "@/config/user-permissions";
import { shallowMount, createLocalVue } from "@vue/test-utils";
import GenerateMissionAppBanner from "@/components/GenerateMissionAppBanner";
const vuetify = new Vuetify();
vuetify.framework.breakpoint = {
init: jest.fn(),
framework: {},
lgAndUp: true,
};
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({
state: {
user: {
user: { name: "mock user", permission: Permission.USER },
},
userPreferences: {
userPreference: {
darkMode: true,
welcomeMessage: true,
},
},
},
});
let wrapper;
beforeEach(() => {
wrapper = shallowMount(GenerateMissionAppBanner, {
store,
localVue,
vuetify,
});
});
describe("GenerateMissionAppBanner", () => {
it("should add lgAndUp classes to container on larger screens", () => {
expect(wrapper.vm.containerClass).toContain("d-flex");
});
it("should not add lgAndUp classes to container on smaller screens", () => {
vuetify.framework.breakpoint.lgAndUp = false;
expect(wrapper.vm.containerClass).not.toContain("d-flex");
});
it("should emit true to show-mission-app-form when the button is clicked", async () => {
jest.spyOn(wrapper.vm, "$emit");
await wrapper.findComponent({ name: "v-btn" }).trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.vm.$emit).toHaveBeenLastCalledWith(
"show-mission-app-form",
true
);
});
});
import Vuex from "vuex";
import { shallowMount, createLocalVue } from "@vue/test-utils";
import GenerateMissionAppHeader from "@/components/GenerateMissionAppHeader";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("GenerateMissionAppHeader", () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(GenerateMissionAppHeader, { localVue });
});
it("displays the mission app header", () => {
expect(wrapper.html()).toContain("Generate Mission Application");
});
});
import Vuex from "vuex";
import { shallowMount, createLocalVue } from "@vue/test-utils";
import GenerateMissionApplication from "@/components/GenerateMissionApplication";
import MissionAppApi from "@/api/services/missionApp";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("GenerateMissionApplication", () => {
let wrapper;
beforeEach(() => {
jest.spyOn(MissionAppApi, "postMissionApp").mockResolvedValue();
wrapper = shallowMount(
GenerateMissionApplication,
{
localVue,
mocks: {
$store: {},
},
}
);
jest.spyOn(wrapper.vm, "$emit");
});
it("emits false to the show-mission-app-form event", () => {
wrapper.vm.hideMissionAppForm();
expect(wrapper.vm.$emit).toHaveBeenLastCalledWith(
"show-mission-app-form",
false
);
});
it("Closes the form when submitting", async () => {
await wrapper.vm.submitMissionAppForm();
expect(wrapper.vm.$emit).toHaveBeenLastCalledWith(
"show-mission-app-form",
false
);
});
it("should handle error", async () => {
wrapper.vm.$store.commit = jest.fn();
console.error = jest.fn();
jest.spyOn(MissionAppApi, "postMissionApp").mockRejectedValue();
await wrapper.vm.submitMissionAppForm();
expect(console.error).toHaveBeenCalled();
expect(wrapper.vm.$store.commit).toHaveBeenCalled();
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment