Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e31adb9db | ||
|
|
f0feb9e563 | ||
|
|
44624e3820 | ||
|
|
225c94b853 | ||
|
|
bd1b8542eb | ||
|
|
c92e93ab1c | ||
|
|
1358727832 | ||
|
|
9bc84a2c59 | ||
|
|
7f00a83b19 | ||
|
|
19426cfd51 | ||
|
|
30c05eb834 | ||
|
|
f1d334b848 | ||
|
|
961f497b6b | ||
|
|
89f33f3714 | ||
|
|
4bad52f43a | ||
|
|
fd6cd3ddf7 | ||
|
|
adf7f8603b | ||
|
|
2a2e4350ee | ||
|
|
f563a4ef45 | ||
|
|
37b6ef6d3f | ||
|
|
96ff0ddf27 | ||
|
|
746862a897 | ||
|
|
5d6e65f206 | ||
|
|
6f9a464302 | ||
|
|
abf7866292 | ||
|
|
10c98c66e7 | ||
|
|
c1f7fe2ddd | ||
|
|
7f4586ed9e | ||
|
|
0b6049c8b0 | ||
|
|
2d347171a9 | ||
|
|
a085d4d4f7 | ||
|
|
7e2f3e3df3 | ||
|
|
3899daea7c | ||
|
|
3d1ee732b4 | ||
|
|
899d7beee0 | ||
|
|
4c7f3fad75 | ||
|
|
b8eb2c3c9b | ||
|
|
f5d612c8b5 | ||
|
|
4020f2d4e2 | ||
|
|
5794cfc7b2 | ||
|
|
1284a16978 | ||
|
|
f255a470cd | ||
|
|
f738e71657 | ||
|
|
beeee851ac | ||
|
|
eb365f0d39 | ||
|
|
57e614e8db | ||
|
|
738fa1f15f | ||
|
|
4c959ab297 | ||
|
|
e685295d04 | ||
|
|
204bd10e04 | ||
|
|
5a302b0bcc | ||
|
|
daa23f66c4 | ||
|
|
b06c396086 | ||
|
|
5555603a5c | ||
|
|
7c3b6d6d9e | ||
|
|
ce87d7728c | ||
|
|
e4da851172 | ||
|
|
e3433ec1f7 | ||
|
|
96f30d1b31 | ||
|
|
f4a48467e3 | ||
|
|
bada405511 | ||
|
|
96d7c055b0 | ||
|
|
a747614a74 | ||
|
|
84856407ee | ||
|
|
9bfd624713 | ||
|
|
b2a6e4d47f | ||
|
|
308240be89 | ||
|
|
c12edc7f73 | ||
|
|
44bd5ee1c6 | ||
|
|
8f3cccce32 | ||
|
|
1ef7d9b5a7 | ||
|
|
c445f95aa4 | ||
|
|
96db738f4b | ||
|
|
6181df4c69 | ||
|
|
376787fda1 | ||
|
|
80ffa856bd | ||
|
|
eb9fe573b8 | ||
|
|
e222d5b909 | ||
|
|
1c774aa783 | ||
|
|
04b51da60f | ||
|
|
28ddec6898 | ||
|
|
d987f844de | ||
|
|
b0d26fe1e6 | ||
|
|
e96131a933 | ||
|
|
3d58745bf1 | ||
|
|
9560148253 | ||
|
|
ec00efb656 | ||
|
|
ba34307918 | ||
|
|
28d8afae2e | ||
|
|
2e507be454 | ||
|
|
dbf3617224 | ||
|
|
200a7944e6 | ||
|
|
8dafc2b9a0 | ||
|
|
b7559c1f4e | ||
|
|
dd8e1864db | ||
|
|
d85a84980f | ||
|
|
4295966b7f | ||
|
|
a87c079ade | ||
|
|
b3604e3e40 | ||
|
|
6be8130329 | ||
|
|
ca3972c04f | ||
|
|
4d98729cde | ||
|
|
185af6f00e | ||
|
|
c5353d6e5e | ||
|
|
20a05dc96a | ||
|
|
30028b5ba0 | ||
|
|
3bafb708ea | ||
|
|
24d86a230a | ||
|
|
620f822504 | ||
|
|
93a57f26ee | ||
|
|
40ec4493ea | ||
|
|
de83d59bde | ||
|
|
a937c5e8de | ||
|
|
b820bb19d7 | ||
|
|
36db2d3f1d | ||
|
|
5786b86521 | ||
|
|
c6a5f93b44 | ||
|
|
8814e2eacb | ||
|
|
9b1e4a8291 | ||
|
|
01c2468f86 | ||
|
|
af705718ff | ||
|
|
e22ef12529 | ||
|
|
62510d9243 | ||
|
|
0fd25a6bb4 | ||
|
|
a268a7b75e | ||
|
|
3dded0a7bc | ||
|
|
ab3140be0a | ||
|
|
0fcd58c080 | ||
|
|
52f0d9f9e8 | ||
|
|
6585fa8e41 | ||
|
|
bef9b3b276 | ||
|
|
af411b854a | ||
|
|
a697dba5b9 | ||
|
|
1203081ec7 | ||
|
|
2db63ae2f8 | ||
|
|
9dd1617b57 | ||
|
|
834058ff45 | ||
|
|
0f41a0a576 | ||
|
|
1025cbfb87 | ||
|
|
ca3c1c7623 | ||
|
|
69d127a89e | ||
|
|
b4d9cd5bb7 | ||
|
|
4b1327db24 | ||
|
|
2f2ceed24e | ||
|
|
de0f20809d | ||
|
|
b1f5857cac | ||
|
|
ddbd1ca163 | ||
|
|
777e40fff8 | ||
|
|
68460331f5 | ||
|
|
32e9fe8b6a | ||
|
|
b3a2460b3f | ||
|
|
3260bf1b2a | ||
|
|
e69c6456d1 | ||
|
|
f8b3792e62 | ||
|
|
01c34c0a79 | ||
|
|
825751239f | ||
|
|
2465739060 | ||
|
|
e6834d2033 | ||
|
|
924100df4c | ||
|
|
1ac7d1e08b | ||
|
|
7becccd628 | ||
|
|
dff5cac4fe | ||
|
|
ecd0c1de20 | ||
|
|
878cb17b62 | ||
|
|
706981c231 | ||
|
|
d212cf7c40 | ||
|
|
19b6e5154f | ||
|
|
89263918ea | ||
|
|
e8fc6d163b | ||
|
|
34a670f8d7 | ||
|
|
6a46e2dd57 | ||
|
|
5e0fa7c744 | ||
|
|
474ce17a13 | ||
|
|
f00eed8633 | ||
|
|
42bcefa068 | ||
|
|
29bde998ed | ||
|
|
e29a83ec25 | ||
|
|
ddfcbde7d4 | ||
|
|
42e31d3ece | ||
|
|
cd8480bc67 | ||
|
|
06dc05be08 | ||
|
|
d3fda81af3 | ||
|
|
f58d345b63 | ||
|
|
f35382a4e0 | ||
|
|
8087430dd2 | ||
|
|
2a5f1f3668 | ||
|
|
a889cb30e0 | ||
|
|
e411c0ab7b | ||
|
|
a136371c1f | ||
|
|
83fa213854 | ||
|
|
750cc1ecae | ||
|
|
e25a854d24 | ||
|
|
3b32195271 | ||
|
|
f9411794c4 | ||
|
|
c5fdbfd62d | ||
|
|
f12ae90aba | ||
|
|
20c4bbcee2 | ||
|
|
f3232f34c2 | ||
|
|
8fedb23399 | ||
|
|
4ca519ce62 | ||
|
|
6d4768272a | ||
|
|
e8cc22879f | ||
|
|
b287683a61 | ||
|
|
ea43ee99d6 | ||
|
|
48db68e7ac | ||
|
|
7807b7a280 | ||
|
|
ad0e223742 | ||
|
|
c9a74f2800 | ||
|
|
570ed61edb | ||
|
|
835b302c19 | ||
|
|
a0fad9874a |
No files matched your search
@@ -15,5 +15,7 @@ project {
|
|||||||
"**/coverage/**",
|
"**/coverage/**",
|
||||||
"ui/desktop/electron-app/out/**",
|
"ui/desktop/electron-app/out/**",
|
||||||
"ui/desktop/electron-app/ember-dist/**",
|
"ui/desktop/electron-app/ember-dist/**",
|
||||||
|
"**/pnpm-lock.yaml",
|
||||||
|
"**/pnpm-workspace.yaml",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -11,21 +11,57 @@ assignees: ''
|
|||||||
|
|
||||||
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
||||||
|
|
||||||
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose by contacting us at security@hashicorp.com. Our PGP key is available at [our security page](https://www.hashicorp.com/security/).
|
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose this by contacting us at security@hashicorp.com. Our PGP key is available on [our security page](https://www.hashicorp.com/security/).
|
||||||
|
|
||||||
|
Logging is available on Boundary Desktop Client version >= 2.1.0
|
||||||
|
|
||||||
|
Logs are found at:
|
||||||
|
|
||||||
|
Linux: ~/.config/Boundary/logs/desktop-client.log
|
||||||
|
MacOS: ~/Library/Logs/Boundary/desktop-client.log
|
||||||
|
Windows: $env:USERPROFILE\AppData\Roaming\Boundary\logs\desktop-client.log
|
||||||
|
|
||||||
|
If requests are taking longer than expected, please change the logging level to DEBUG. This will log the cache daemon request times as well.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
**Describe the bug**
|
## :bug: Bug Report
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
### **Description**
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Run `boundary authenticate ...`
|
|
||||||
2. Run `boundary connect ...`
|
|
||||||
3. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
_A clear and concise description of the bug._
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Additional context**
|
### **Steps to Reproduce**
|
||||||
Add any other context about the problem here.
|
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '...'
|
||||||
|
3. Scroll down to '...'
|
||||||
|
4. See the error
|
||||||
|
|
||||||
|
## **Expected behavior**
|
||||||
|
|
||||||
|
_A clear description of what you expected to happen._
|
||||||
|
|
||||||
|
## **Actual behavior**
|
||||||
|
|
||||||
|
_A clear description of what actually happened._
|
||||||
|
|
||||||
|
## **Screenshots (if applicable)**
|
||||||
|
|
||||||
|
_Add screenshots or screen recordings to help explain the issue._
|
||||||
|
|
||||||
|
## **Environment**
|
||||||
|
|
||||||
|
- **OS and Version:** (e.g., Windows 10 (22H2), macOS (14.7.2))
|
||||||
|
- **Architecture:** (e.g., arm64, x86_64)
|
||||||
|
- **Browser/Version:** (if applicable)
|
||||||
|
- **Desktop Client Version:** (if applicable)
|
||||||
|
- **Controller/worker versions:** (if applicable)
|
||||||
|
|
||||||
|
## **Logs & Console Output (if applicable)**
|
||||||
|
|
||||||
|
_Paste any relevant error logs or browser console output here._
|
||||||
|
|
||||||
|
## **Additional Context**
|
||||||
|
|
||||||
|
_Any other details that might be relevant._
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
@@ -18,3 +18,11 @@
|
|||||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
|
|
||||||
|
## PCI review checklist
|
||||||
|
|
||||||
|
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
|
||||||
|
- [ ] I have documented a clear reason for, and description of, the change I am making.
|
||||||
|
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
|
||||||
|
- [ ] If applicable, I've documented the impact of any changes to security controls.
|
||||||
|
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
|
||||||
@@ -27,7 +27,6 @@ echo "Trying to merge the latest oss commit - ${latest_oss_commit} from oss/${BR
|
|||||||
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
|
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
|
||||||
# Here we can try to auto resolve conflicts
|
# Here we can try to auto resolve conflicts
|
||||||
# This logic will vary from project to project. The below is just an example.
|
# This logic will vary from project to project. The below is just an example.
|
||||||
# We'll try to resolve package.json and yarn.lock conflicts by taking the OSS changes
|
|
||||||
conflicts=$(git diff --name-only --diff-filter=U)
|
conflicts=$(git diff --name-only --diff-filter=U)
|
||||||
printf "Found conflicts:\n${conflicts}\n"
|
printf "Found conflicts:\n${conflicts}\n"
|
||||||
# Resolve package.json conflicts by taking oss
|
# Resolve package.json conflicts by taking oss
|
||||||
@@ -36,11 +35,12 @@ if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit $
|
|||||||
git checkout --theirs package.json
|
git checkout --theirs package.json
|
||||||
git add package.json
|
git add package.json
|
||||||
fi
|
fi
|
||||||
# Resolve yarn.lock conflicts by taking oss
|
# Resolve pnpm-lock.yaml conflicts by re-running pnpm install:
|
||||||
if echo "$conflicts" | grep -q yarn.lock; then
|
# https://pnpm.io/git#merge-conflicts
|
||||||
echo "Checking out oss yarn.lock..."
|
if echo "$conflicts" | grep -q pnpm-lock.yaml; then
|
||||||
git checkout --theirs yarn.lock
|
echo "Re-running pnpm install to fix conflicts by regenerating pnpm-lock.yaml..."
|
||||||
git add yarn.lock
|
pnpm install
|
||||||
|
git add pnpm-lock.yaml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If we can commit all the changes without conflict, we have resolved everything automatically
|
# If we can commit all the changes without conflict, we have resolved everything automatically
|
||||||
|
|||||||
@@ -9,91 +9,86 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
dependencies:
|
dependencies:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
build-admin-ui-oss:
|
build-admin-ui-oss:
|
||||||
name: Build Admin UI OSS
|
name: Build Admin UI OSS
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:oss
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:oss
|
||||||
- name: Upload artifact admin-ui
|
- name: Upload artifact admin-ui
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui
|
name: admin-ui
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
- name: Upload artifact OSS admin UI
|
- name: Upload artifact OSS admin UI
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui-oss
|
name: admin-ui-oss
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
build-admin-ui-enterprise:
|
build-admin-ui-enterprise:
|
||||||
name: Build Admin UI Enterprise
|
name: Build Admin UI Enterprise
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:enterprise
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:enterprise
|
||||||
- name: Upload artifact enterprise admin UI
|
- name: Upload artifact enterprise admin UI
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui-ent
|
name: admin-ui-ent
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
build-admin-ui-hcp:
|
build-admin-ui-hcp:
|
||||||
name: Build Admin UI HCP
|
name: Build Admin UI HCP
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:hcp
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:hcp
|
||||||
- name: Upload artifact HCP admin UI
|
- name: Upload artifact HCP admin UI
|
||||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui-hcp
|
name: admin-ui-hcp
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -27,18 +27,18 @@ jobs:
|
|||||||
language: ['javascript']
|
language: ['javascript']
|
||||||
# Learn more...
|
# Learn more...
|
||||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0
|
uses: github/codeql-action/init@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
@@ -54,4 +54,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0
|
uses: github/codeql-action/analyze@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||||
@@ -2,7 +2,7 @@ name: Add Copyright Headers
|
|||||||
on: pull_request
|
on: pull_request
|
||||||
jobs:
|
jobs:
|
||||||
copywrite:
|
copywrite:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
@@ -10,6 +10,6 @@ jobs:
|
|||||||
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
|
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
|
||||||
- name: Add missing Copyright Headers
|
- name: Add missing Copyright Headers
|
||||||
run: copywrite headers
|
run: copywrite headers
|
||||||
- uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
|
- uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1
|
||||||
with:
|
with:
|
||||||
commit_message: Add missing copyright headers
|
commit_message: Add missing copyright headers
|
||||||
@@ -11,7 +11,7 @@ name: Jira Sync
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
name: Jira sync
|
name: Jira sync
|
||||||
steps:
|
steps:
|
||||||
- name: Check if community user
|
- name: Check if community user
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -4,71 +4,71 @@ jobs:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
compliance:
|
compliance:
|
||||||
name: License Compliance
|
name: License Compliance
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run compliance:licenses
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run compliance:licenses
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
needs: [dependencies, compliance]
|
needs: [dependencies, compliance]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn lint
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: browser-actions/setup-chrome@c785b87e244131f27c9f19c1a33e2ead956ab7ce # v1.7.3
|
||||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
install-dependencies: true
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn test
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm test
|
||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
trigger-oss-merge:
|
trigger-oss-merge:
|
||||||
# Run this only on merge events in OSS repo
|
# Run this only on merge events in OSS repo
|
||||||
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
|
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Merge
|
- name: Trigger Merge
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
types: [ oss-merge ]
|
types: [ oss-merge ]
|
||||||
jobs:
|
jobs:
|
||||||
oss-merge:
|
oss-merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: Test E2E Desktop Client
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, opened, synchronize, reopened]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
if: ${{contains(github.event.pull_request.labels.*.name, 'desktop')}}
|
||||||
|
name: Run Desktop E2E Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
ENOS_VAR_boundary_edition: "ent"
|
||||||
|
ENOS_VAR_e2e_debug_no_run: true
|
||||||
|
ENOS_VAR_boundary_license_path: "./license.hclic"
|
||||||
|
SETUP_CLI: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: |
|
||||||
|
pnpm-lock.yaml
|
||||||
|
ui/desktop/electron-app/pnpm-lock.yaml
|
||||||
|
|
||||||
|
- name: Set up Terraform
|
||||||
|
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
|
||||||
|
|
||||||
|
- name: Set up Enos
|
||||||
|
uses: hashicorp/action-setup-enos@b9fa53484a1e8fdcc7b02a118bcf01d65b9414c9
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install Vault CLI
|
||||||
|
run: |
|
||||||
|
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/vault.zip
|
||||||
|
unzip /tmp/vault.zip -d /usr/local/bin
|
||||||
|
|
||||||
|
- name: Checkout boundary-enterprise
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: hashicorp/boundary-enterprise
|
||||||
|
# ref: ${{ github.event.inputs.boundary-enterprise-branch }}
|
||||||
|
path: support/src/boundary-enterprise
|
||||||
|
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: GH fix for localhost resolution
|
||||||
|
run: |
|
||||||
|
cat /etc/hosts && echo "-----------"
|
||||||
|
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
|
||||||
|
cat /etc/hosts
|
||||||
|
ssh -V
|
||||||
|
|
||||||
|
echo "127.0.0.1 localhost boundary" | sudo tee -a /etc/hosts
|
||||||
|
echo "127.0.0.1 localhost worker" | sudo tee -a /etc/hosts
|
||||||
|
echo "127.0.0.1 localhost vault" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
# TODO: Build image ourselves in case we don't want to use latest?
|
||||||
|
- name: Get Boundary docker image
|
||||||
|
run: |
|
||||||
|
docker pull hashicorp/boundary-enterprise:latest
|
||||||
|
docker save hashicorp/boundary-enterprise:latest | gzip > /tmp/boundary-enterprise.zip
|
||||||
|
|
||||||
|
# TODO: Set up license in a better way? Use directly rather than writing to file and pull from secrets manager
|
||||||
|
- name: Set up test infra
|
||||||
|
id: infra
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.BOUNDARY_ENT_LICENSE }}" > ./support/src/boundary-enterprise/enos/license.hclic
|
||||||
|
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
|
||||||
|
mkdir -p ./enos/terraform-plugin-cache
|
||||||
|
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||||
|
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||||
|
export ENOS_VAR_boundary_docker_image_name=hashicorp/boundary-enterprise:latest && \
|
||||||
|
export ENOS_VAR_boundary_docker_image_file=/tmp/boundary-enterprise.zip && \
|
||||||
|
enos scenario launch --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
cd e2e-tests
|
||||||
|
pnpm playwright install --with-deps
|
||||||
|
|
||||||
|
# Runner is headless so we need to enable a display to run the client
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
source <(bash ./support/src/boundary-enterprise/enos/scripts/test_e2e_env.sh)
|
||||||
|
cd e2e-tests
|
||||||
|
export DISPLAY=:99
|
||||||
|
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||||
|
pnpm run desktop
|
||||||
|
|
||||||
|
- name: Upload Playwright report
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: e2e-tests/playwright-report
|
||||||
|
|
||||||
|
- name: Clean up test infra
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||||
|
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||||
|
enos scenario destroy --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger-gha-release:
|
trigger-gha-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Release description
|
- name: Release description
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ coverage-*
|
|||||||
libpeerconnection.log
|
libpeerconnection.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
testem.log
|
testem.log
|
||||||
yarn-error.log
|
|
||||||
.watchman-cookie*
|
.watchman-cookie*
|
||||||
|
|
||||||
### VisualStudioCode ###
|
### VisualStudioCode ###
|
||||||
|
|||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
yarn compliance:licenses
|
pnpm compliance:licenses
|
||||||
npx lint-staged
|
pnpm lint-staged
|
||||||
@@ -21,4 +21,3 @@ else
|
|||||||
# This is to allow interaction with the terminal for commitizen
|
# This is to allow interaction with the terminal for commitizen
|
||||||
exec < /dev/tty && npx git-cz --hook || true
|
exec < /dev/tty && npx git-cz --hook || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Binary file not shown.
@@ -25,7 +25,7 @@ subproject, e.g. running or testing.
|
|||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Optional Tooling](#optional-tooling)
|
- [Optional Tooling](#optional-tooling)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Yarn Commands](#yarn-commands)
|
- [Pnpm Commands](#pnpm-commands)
|
||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
- [Branching](#branching)
|
- [Branching](#branching)
|
||||||
- [Building ToC](#building-toc)
|
- [Building ToC](#building-toc)
|
||||||
@@ -33,8 +33,8 @@ subproject, e.g. running or testing.
|
|||||||
- [Building with a Container](#building-with-a-container)
|
- [Building with a Container](#building-with-a-container)
|
||||||
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
|
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
|
||||||
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
|
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
|
||||||
- [For admin](#for-admin)
|
- [For Admin](#for-admin)
|
||||||
- [For desktop](#for-desktop)
|
- [For Desktop](#for-desktop)
|
||||||
- [Committing](#committing)
|
- [Committing](#committing)
|
||||||
- [License Checking](#license-checking)
|
- [License Checking](#license-checking)
|
||||||
|
|
||||||
@@ -67,8 +67,8 @@ You will need the following things properly installed on your computer.
|
|||||||
|
|
||||||
* [Git][git]
|
* [Git][git]
|
||||||
* [Node.js][node]
|
* [Node.js][node]
|
||||||
* Supported versions: `v18` and `v20`.
|
* Supported versions: `v20` and `v22`.
|
||||||
* [Yarn][yarn] installed globally
|
* [Pnpm][pnpm] installed globally
|
||||||
* [Google Chrome][chrome]
|
* [Google Chrome][chrome]
|
||||||
* [Firefox][firefox]
|
* [Firefox][firefox]
|
||||||
|
|
||||||
@@ -90,18 +90,18 @@ that best suit you and your personal style.
|
|||||||
|
|
||||||
* `git clone <repository-url>` this repository
|
* `git clone <repository-url>` this repository
|
||||||
* `cd boundary-ui`
|
* `cd boundary-ui`
|
||||||
* `yarn install`
|
* `pnpm install`
|
||||||
|
|
||||||
## Yarn Commands
|
## Pnpm Commands
|
||||||
|
|
||||||
List of available project commands. `yarn run <command-name>`
|
List of available project commands. `pnpm run <command-name>`
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------------------|---|
|
|---------------------|---|
|
||||||
| build | Builds Admin UI for production. |
|
| build | Builds Admin UI for production. |
|
||||||
| test | Runs tests in CI mode. |
|
| test | Runs tests in CI mode. |
|
||||||
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
|
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
|
||||||
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
|
| compliance:audit | Checks for issues using `pnpm audit` with moderate and above criticality.|
|
||||||
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
|
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
|
||||||
| doc:toc | Automatically generates a table of contents for the README file. |
|
| doc:toc | Automatically generates a table of contents for the README file. |
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ We follow [GitHub Flow][github-flow].
|
|||||||
### Building ToC
|
### Building ToC
|
||||||
|
|
||||||
To autogenerate a ToC (table of contents) for this README,
|
To autogenerate a ToC (table of contents) for this README,
|
||||||
run `yarn doc:toc`. Please update the ToC whenever editing the structure
|
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
|
||||||
of README.
|
of README.
|
||||||
|
|
||||||
### Building Admin UI for Production
|
### Building Admin UI for Production
|
||||||
@@ -126,8 +126,8 @@ necessary for your target environment, as described in the [Environment Variable
|
|||||||
To quickly produce a production build of Admin UI, run from this folder:
|
To quickly produce a production build of Admin UI, run from this folder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
pnpm install
|
||||||
yarn build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
Assets are saved to `ui/admin/dist/`.
|
Assets are saved to `ui/admin/dist/`.
|
||||||
@@ -151,20 +151,20 @@ necessary for your target environment, as described in the [Environment Variable
|
|||||||
To quickly produce a production build of Desktop UI, run from this folder:
|
To quickly produce a production build of Desktop UI, run from this folder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
pnpm install
|
||||||
yarn build:ui:desktop
|
pnpm build:ui:desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
In windows, UI is generated using docker to provide a stable UI across platforms.
|
In windows, UI is generated using docker to provide a stable UI across platforms.
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
yarn install
|
pnpm install
|
||||||
docker-compose -f docker-compose-embedding.yml run build-desktop-production
|
docker-compose -f docker-compose-embedding.yml run build-desktop-production
|
||||||
yarn build:ui:desktop:app
|
pnpm build:ui:desktop:app
|
||||||
```
|
```
|
||||||
|
|
||||||
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
|
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
|
||||||
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn build:ui:desktop:app`.
|
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `pnpm build:ui:desktop:app`.
|
||||||
|
|
||||||
Assets are saved to `ui/desktop/electron-app/out/make/`.
|
Assets are saved to `ui/desktop/electron-app/out/make/`.
|
||||||
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
|
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
|
||||||
@@ -180,7 +180,7 @@ This assumes you already have up and running a Boundary dev instance in your loc
|
|||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/admin`.
|
This assumes you are within `boundary-ui/ui/admin`.
|
||||||
|
|
||||||
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 yarn start`.
|
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 pnpm start`.
|
||||||
|
|
||||||
Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ Once you open the UI you will see the login screen, authenticate using generated
|
|||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/desktop`.
|
This assumes you are within `boundary-ui/ui/desktop`.
|
||||||
|
|
||||||
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start:desktop`.
|
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false pnpm start:desktop`.
|
||||||
|
|
||||||
In login screen, authenticate using generated admin login name and password.
|
In login screen, authenticate using generated admin login name and password.
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ Enter the cluster URL of your Boundary dev instance, by default is: `http://loca
|
|||||||
### Committing
|
### Committing
|
||||||
|
|
||||||
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
|
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
|
||||||
commit is completed. Simply do a `yarn install` to make sure the hooks are installed.
|
commit is completed. Simply do a `pnpm install` to make sure the hooks are installed.
|
||||||
|
|
||||||
Use a normal `git commit` to go through the checks, if you need to skip these checks,
|
Use a normal `git commit` to go through the checks, if you need to skip these checks,
|
||||||
use `git commit --no-verify` to bypass them. However, a consistent commit message will
|
use `git commit --no-verify` to bypass them. However, a consistent commit message will
|
||||||
@@ -218,10 +218,10 @@ will not be accepted.
|
|||||||
|
|
||||||
[git]: https://git-scm.com/
|
[git]: https://git-scm.com/
|
||||||
[node]: https://nodejs.org/
|
[node]: https://nodejs.org/
|
||||||
[yarn]: https://classic.yarnpkg.com/lang/en/
|
[pnpm]: https://pnpm.io
|
||||||
[chrome]: https://google.com/chrome/
|
[chrome]: https://google.com/chrome/
|
||||||
[firefox]: https://firefox.com/
|
[firefox]: https://firefox.com/
|
||||||
[yarn-workspaces]: https://classic.yarnpkg.com/en/docs/workspaces/
|
[pnpm-workspaces]: https://pnpm.io/workspaces
|
||||||
[homebrew]: https://brew.sh
|
[homebrew]: https://brew.sh
|
||||||
[warp]: https://www.warp.dev
|
[warp]: https://www.warp.dev
|
||||||
[vscode]: https://code.visualstudio.com/
|
[vscode]: https://code.visualstudio.com/
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{diff,md}]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.hbs]
|
[*.hbs]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.{diff,md}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
/**
|
|
||||||
Ember CLI sends analytics information by default. The data is completely
|
|
||||||
anonymous, but there are times when you might want to disable this behavior.
|
|
||||||
|
|
||||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
|
||||||
*/
|
|
||||||
"disableAnalytics": false,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
||||||
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
# unconventional js
|
# unconventional js
|
||||||
/blueprints/*/files/
|
/blueprints/*/files/
|
||||||
/vendor/
|
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
|
/declarations/
|
||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/coverage/
|
/coverage/
|
||||||
!.*
|
!.*
|
||||||
.*/
|
.*/
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -56,7 +56,7 @@ module.exports = {
|
|||||||
extends: ['plugin:n/recommended'],
|
extends: ['plugin:n/recommended'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test files:
|
// test files
|
||||||
files: ['tests/**/*-test.{js,ts}'],
|
files: ['tests/**/*-test.{js,ts}'],
|
||||||
extends: ['plugin:qunit/recommended'],
|
extends: ['plugin:qunit/recommended'],
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist/
|
||||||
|
/declarations/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.env*
|
||||||
|
/.pnp*
|
||||||
|
/.eslintcache
|
||||||
|
/coverage/
|
||||||
|
/testem.log
|
||||||
|
|
||||||
|
# ember-try
|
||||||
|
/.node_modules.ember-try/
|
||||||
|
/package.json.ember-try
|
||||||
|
|
||||||
|
# broccoli-debug
|
||||||
|
/DEBUG/
|
||||||
+2
-12
@@ -2,11 +2,7 @@
|
|||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
/tmp/
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/.bowerrc
|
|
||||||
/.editorconfig
|
/.editorconfig
|
||||||
/.ember-cli
|
/.ember-cli
|
||||||
/.env*
|
/.env*
|
||||||
@@ -19,21 +15,15 @@
|
|||||||
/.prettierignore
|
/.prettierignore
|
||||||
/.prettierrc.js
|
/.prettierrc.js
|
||||||
/.template-lintrc.js
|
/.template-lintrc.js
|
||||||
/.travis.yml
|
|
||||||
/.watchmanconfig
|
/.watchmanconfig
|
||||||
/bower.json
|
|
||||||
/CONTRIBUTING.md
|
/CONTRIBUTING.md
|
||||||
/ember-cli-build.js
|
/ember-cli-build.js
|
||||||
/testem.js
|
/testem.js
|
||||||
/tests/
|
/tests/
|
||||||
/yarn-error.log
|
/tsconfig.declarations.json
|
||||||
/yarn.lock
|
/tsconfig.json
|
||||||
.gitkeep
|
.gitkeep
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
/package.json.ember-try
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
# unconventional js
|
# unconventional js
|
||||||
/blueprints/*/files/
|
/blueprints/*/files/
|
||||||
/vendor/
|
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/coverage/
|
/coverage/
|
||||||
!.*
|
!.*
|
||||||
.eslintcache
|
.*/
|
||||||
.lint-todo/
|
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"ignore_dirs": ["tmp", "dist"]
|
"ignore_dirs": ["dist"]
|
||||||
}
|
}
|
||||||
+15
-15
@@ -8,7 +8,7 @@ This addon contains the API data access layer for Boundary.
|
|||||||
|
|
||||||
- [Add API to an App](#add-api-to-an-app)
|
- [Add API to an App](#add-api-to-an-app)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Yarn Commands](#yarn-commands)
|
- [Pnpm Commands](#pnpm-commands)
|
||||||
- [Linting](#linting)
|
- [Linting](#linting)
|
||||||
- [Formatting](#formatting)
|
- [Formatting](#formatting)
|
||||||
- [Running tests](#running-tests)
|
- [Running tests](#running-tests)
|
||||||
@@ -21,7 +21,7 @@ This addon contains the API data access layer for Boundary.
|
|||||||
## Add API to an App
|
## Add API to an App
|
||||||
|
|
||||||
Add this addon to an Ember application's `devDependencies` as:
|
Add this addon to an Ember application's `devDependencies` as:
|
||||||
`"api": "*"`, for applications included in this monorepo.
|
`"api": "workspace:*"`, for applications included in this monorepo.
|
||||||
|
|
||||||
Since this addon also includes Mirage mocks, be sure to install
|
Since this addon also includes Mirage mocks, be sure to install
|
||||||
`ember-cli-mirage` and add the following config to your UI project:
|
`ember-cli-mirage` and add the following config to your UI project:
|
||||||
@@ -38,9 +38,9 @@ See monorepo README for installation instructions.
|
|||||||
|
|
||||||
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
|
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
|
||||||
|
|
||||||
## Yarn Commands
|
## Pnpm Commands
|
||||||
|
|
||||||
List of available project commands. `yarn run <command-name>`
|
List of available project commands. `pnpm run <command-name>`
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------- | ----------- |
|
| ------- | ----------- |
|
||||||
@@ -64,28 +64,28 @@ Additional commands in the monorepo package may affect this projects.
|
|||||||
|
|
||||||
## Linting
|
## Linting
|
||||||
|
|
||||||
* `yarn lint:fix`
|
* `pnpm lint:fix`
|
||||||
* `yarn lint:hbs`
|
* `pnpm lint:hbs`
|
||||||
* `yarn lint:hbs:fix`
|
* `pnpm lint:hbs:fix`
|
||||||
* `yarn lint:js`
|
* `pnpm lint:js`
|
||||||
* `yarn lint:js:fix`
|
* `pnpm lint:js:fix`
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
||||||
Before submitting your work, be sure to run auto-formatters
|
Before submitting your work, be sure to run auto-formatters
|
||||||
(see commands above). This helps to ensure consistency among authors.
|
(see commands above). This helps to ensure consistency among authors.
|
||||||
|
|
||||||
* `yarn format`
|
* `pnpm format`
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
* `yarn test` – Runs the test suite on the current Ember version
|
* `pnpm test` – Runs the test suite on the current Ember version
|
||||||
* `yarn test --server` – Runs the test suite in "watch mode"
|
* `pnpm test --server` – Runs the test suite in "watch mode"
|
||||||
* `yarn test:all` – Runs the test suite against multiple Ember versions
|
* `pnpm test:all` – Runs the test suite against multiple Ember versions
|
||||||
|
|
||||||
## Running the dummy application
|
## Running the dummy application
|
||||||
|
|
||||||
* `yarn start`
|
* `pnpm start`
|
||||||
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
|
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
@@ -95,5 +95,5 @@ See monorepo README for more contribution instructions.
|
|||||||
### Building ToC
|
### Building ToC
|
||||||
|
|
||||||
To autogenerate a ToC (table of contents) for this README,
|
To autogenerate a ToC (table of contents) for this README,
|
||||||
run `yarn doc:toc`. Please update the ToC whenever editing the structure
|
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
|
||||||
of README.
|
of README.
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
* @param hooks
|
* @param hooks
|
||||||
*/
|
*/
|
||||||
export function setupIndexedDb(hooks) {
|
export function setupIndexedDb(hooks) {
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(async function () {
|
||||||
const indexedDb = this.owner.lookup('service:indexed-db');
|
const indexedDb = this.owner.lookup('service:indexed-db');
|
||||||
indexedDb.setup(`test-indexed-db-${Date.now()}`);
|
await indexedDb.setup(`test-indexed-db-${Date.now()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks.afterEach(async function () {
|
hooks.afterEach(async function () {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides abilities for channel recordings.
|
* Provides abilities for channel recordings.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
/**
|
/**
|
||||||
* Provides abilities for credential store.
|
* Provides abilities for credential store.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
class InvalidRolePrincipalTypeError extends Error {
|
class InvalidRolePrincipalTypeError extends Error {
|
||||||
name = 'InvalidRolePrincipalTypeError';
|
name = 'InvalidRolePrincipalTypeError';
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
* @return {Promise} promise
|
* @return {Promise} promise
|
||||||
*/
|
*/
|
||||||
async query(store, schema, query) {
|
async query(store, schema, query) {
|
||||||
const queryObject = structuredClone(query);
|
const { batchLimit, ...queryObject } = query;
|
||||||
let result;
|
let result;
|
||||||
let data = [];
|
let data = [];
|
||||||
|
|
||||||
@@ -126,8 +126,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
result = await super.query(store, schema, query);
|
result = await super.query(store, schema, query);
|
||||||
return prenormalizeArrayResponse(result);
|
return prenormalizeArrayResponse(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this loop as long as the response_type is delta,
|
// Run this loop as long as the response_type is delta,
|
||||||
// which indicates that there are more items in the list
|
// which indicates that there are more items in the list
|
||||||
|
// or the batch size has not been reached
|
||||||
do {
|
do {
|
||||||
result = await super.query(store, schema, queryObject);
|
result = await super.query(store, schema, queryObject);
|
||||||
//add the result items to a data array
|
//add the result items to a data array
|
||||||
@@ -136,7 +138,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
//pass in the list token for subsequent calls to fetch the remaining list items
|
//pass in the list token for subsequent calls to fetch the remaining list items
|
||||||
queryObject.list_token = result.list_token;
|
queryObject.list_token = result.list_token;
|
||||||
}
|
}
|
||||||
} while (result && result.response_type === 'delta');
|
} while (
|
||||||
|
result?.response_type === 'delta' &&
|
||||||
|
(!batchLimit || data.length < batchLimit)
|
||||||
|
);
|
||||||
|
|
||||||
result.items = data;
|
result.items = data;
|
||||||
return prenormalizeArrayResponse(result);
|
return prenormalizeArrayResponse(result);
|
||||||
@@ -163,7 +168,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides default ajax method by rewritting PUT requests as PATCH.
|
* Overrides default ajax method by rewriting PUT requests as PATCH.
|
||||||
* PATCH is the request method used by our API for updates.
|
* PATCH is the request method used by our API for updates.
|
||||||
*
|
*
|
||||||
* @override
|
* @override
|
||||||
@@ -291,9 +296,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
const errors = fieldErrors.map((error) => ({
|
const errors = fieldErrors.map((error) => ({
|
||||||
detail: error.description,
|
detail: error.description,
|
||||||
// Going forward, nested attributes will be hoisted. Thus "attributes."
|
// Going forward, nested attributes will be hoisted. Thus "attributes."
|
||||||
// must be stripped from the error name key. Until all models are
|
// must be stripped from the error name key.
|
||||||
// transitioned away from model fragments, however, both this and the
|
|
||||||
// prior error key mappings are necessary.
|
|
||||||
source: {
|
source: {
|
||||||
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
|
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export default class GeneratedHostSetModel extends BaseModel {
|
|||||||
name: ['aws', 'azure', 'gcp'],
|
name: ['aws', 'azure', 'gcp'],
|
||||||
},
|
},
|
||||||
emptyArrayIfMissing: true,
|
emptyArrayIfMissing: true,
|
||||||
|
trimWhitespace: true,
|
||||||
})
|
})
|
||||||
preferred_endpoints;
|
preferred_endpoints;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
|
|||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The AWS region to use.',
|
description: 'The AWS region to use.',
|
||||||
|
trimWhitespace: true,
|
||||||
})
|
})
|
||||||
region;
|
region;
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
|
|||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role ARN to use.',
|
description: 'The role ARN to use.',
|
||||||
|
trimWhitespace: true,
|
||||||
})
|
})
|
||||||
role_arn;
|
role_arn;
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global __electronLog */
|
import { service } from '@ember/service';
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import { getOwner, setOwner } from '@ember/application';
|
import { getOwner, setOwner } from '@ember/application';
|
||||||
import { generateMQLExpression } from '../utils/mql-query';
|
import { generateMQLExpression } from '../utils/mql-query';
|
||||||
import { paginateResults } from '../utils/paginate-results';
|
import { paginateResults } from '../utils/paginate-results';
|
||||||
import { underscore } from '@ember/string';
|
import { underscore } from '@ember/string';
|
||||||
|
|
||||||
|
const { __electronLog } = globalThis;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not all types are yet supported by the cache daemon so we'll
|
* Not all types are yet supported by the cache daemon so we'll
|
||||||
* just whitelist the ones we need to start with. Keys are the resource
|
* just whitelist the ones we need to start with. Keys are the resource
|
||||||
@@ -42,7 +43,10 @@ const fetchControllerData = async (context, next) => {
|
|||||||
// If we get an error or cache daemon is unavailable, fall back to calling the API
|
// If we get an error or cache daemon is unavailable, fall back to calling the API
|
||||||
context.request.data.query = remainingQuery;
|
context.request.data.query = remainingQuery;
|
||||||
const results = await next(context.request);
|
const results = await next(context.request);
|
||||||
const models = results.content.toArray();
|
|
||||||
|
// As a result of https://rfcs.emberjs.com/id/0846-ember-data-deprecate-proxies/ we cannot use proxy array methods like .toArray
|
||||||
|
// use slice() instead as indicated in the RFC.
|
||||||
|
const models = results.content.slice();
|
||||||
|
|
||||||
const paginatedResult = paginateResults(models, page, pageSize);
|
const paginatedResult = paginateResults(models, page, pageSize);
|
||||||
paginatedResult.meta = { totalItems: models.length };
|
paginatedResult.meta = { totalItems: models.length };
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { modelIndexes } from 'api/services/indexed-db';
|
import { modelIndexes } from 'api/services/indexed-db';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { getOwner, setOwner } from '@ember/application';
|
import { getOwner, setOwner } from '@ember/application';
|
||||||
import { queryIndexedDb } from '../utils/indexed-db-query';
|
import { queryIndexedDb } from '../utils/indexed-db-query';
|
||||||
import { paginateResults } from '../utils/paginate-results';
|
import { paginateResults } from '../utils/paginate-results';
|
||||||
|
import { sortResults } from '../utils/sort-results';
|
||||||
import { hashCode } from '../utils/hash-code';
|
import { hashCode } from '../utils/hash-code';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,11 +18,15 @@ import { hashCode } from '../utils/hash-code';
|
|||||||
export default class IndexedDbHandler {
|
export default class IndexedDbHandler {
|
||||||
@service indexedDb;
|
@service indexedDb;
|
||||||
|
|
||||||
|
batchLimit = 10_000;
|
||||||
|
|
||||||
constructor(context) {
|
constructor(context) {
|
||||||
setOwner(this, getOwner(context));
|
setOwner(this, getOwner(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
async request(context, next) {
|
async request(context, next) {
|
||||||
|
const { batchLimit } = this;
|
||||||
|
|
||||||
switch (context.request.op) {
|
switch (context.request.op) {
|
||||||
case 'query': {
|
case 'query': {
|
||||||
const { store, data } = context.request;
|
const { store, data } = context.request;
|
||||||
@@ -52,111 +57,89 @@ export default class IndexedDbHandler {
|
|||||||
|
|
||||||
if (!peekIndexedDB) {
|
if (!peekIndexedDB) {
|
||||||
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
||||||
|
let payload;
|
||||||
let listToken;
|
let listToken;
|
||||||
|
let writeToIndexedDbPromise;
|
||||||
|
|
||||||
if (storeToken) {
|
if (storeToken) {
|
||||||
listToken = await indexedDb.token.get(tokenKey);
|
const tokenObj = await indexedDb.token.get(tokenKey);
|
||||||
|
listToken = tokenObj?.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload;
|
do {
|
||||||
try {
|
try {
|
||||||
payload = await adapter.query(store, schema, {
|
|
||||||
...remainingQuery,
|
|
||||||
list_token: listToken?.token,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// If we get an invalid list token, we'll delete the token and
|
|
||||||
// clear the DB and try again without a token.
|
|
||||||
if (
|
|
||||||
e?.errors[0].status === 400 &&
|
|
||||||
e?.errors[0].code === 'invalid list token'
|
|
||||||
) {
|
|
||||||
await indexedDb[type].clear();
|
|
||||||
// Delete all tokens of the same resource type so we don't keep clearing the DB.
|
|
||||||
// Even if other tokens may still be valid, we might have cleared data that they
|
|
||||||
// depended on so we should clear all tokens of the same type when clearing the DB.
|
|
||||||
const tokenKeys = await indexedDb.token
|
|
||||||
.where(':id')
|
|
||||||
.startsWith(type)
|
|
||||||
.primaryKeys();
|
|
||||||
|
|
||||||
await indexedDb.token.bulkDelete(tokenKeys);
|
|
||||||
|
|
||||||
payload = await adapter.query(store, schema, {
|
payload = await adapter.query(store, schema, {
|
||||||
...remainingQuery,
|
...remainingQuery,
|
||||||
|
list_token: listToken,
|
||||||
|
batchLimit: batchLimit,
|
||||||
|
});
|
||||||
|
|
||||||
|
// await the previous writeToIndexedDbPromise before writing to indexedDb again
|
||||||
|
if (writeToIndexedDbPromise) {
|
||||||
|
await writeToIndexedDbPromise;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
payload = await this.retryQueryFailure({
|
||||||
|
err,
|
||||||
|
indexedDb,
|
||||||
|
type,
|
||||||
|
adapter,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
remainingQuery,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
listToken = payload.list_token;
|
||||||
|
|
||||||
// Store the token we just got back from the payload if it exists
|
// Don't await immediately and let the next query run while we write to indexedDb
|
||||||
if (payload.list_token && storeToken) {
|
writeToIndexedDbPromise = this.writeToIndexedDb({
|
||||||
await indexedDb.token.put({
|
payload,
|
||||||
id: tokenKey,
|
storeToken,
|
||||||
token: payload.list_token,
|
indexedDb,
|
||||||
});
|
tokenKey,
|
||||||
}
|
type,
|
||||||
|
|
||||||
// Remove any records from the DB if the API indicates they've been deleted
|
|
||||||
if (payload.removed_ids?.length > 0) {
|
|
||||||
await indexedDb[type].bulkDelete(payload.removed_ids);
|
|
||||||
}
|
|
||||||
// This is a temporary fix of clearing the DB (specifically for auth-methods)
|
|
||||||
// since we are not storing the token we do not get back a list of removed_ids
|
|
||||||
// from the API call and we do not want deleted items from showing in list view.
|
|
||||||
if (!storeToken) {
|
|
||||||
await indexedDb[type].clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedPayload = serializer.normalizeResponse(
|
|
||||||
store,
|
|
||||||
schema,
|
|
||||||
payload,
|
|
||||||
null,
|
|
||||||
'query',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: payloadData } = normalizedPayload;
|
|
||||||
|
|
||||||
// Store the new data we just got back from the API refresh
|
|
||||||
const items = payloadData.map((datum) =>
|
|
||||||
this.indexedDb.normalizeData({
|
|
||||||
data: datum,
|
|
||||||
cleanData: true,
|
|
||||||
schema,
|
|
||||||
serializer,
|
serializer,
|
||||||
}),
|
store,
|
||||||
);
|
schema,
|
||||||
|
});
|
||||||
|
} while (payload.response_type === 'delta');
|
||||||
|
|
||||||
await indexedDb[type].bulkPut(items);
|
await writeToIndexedDbPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Results returned in JSON API reponse, but in a format optimized for indexedDb
|
||||||
const indexedDbResults = await queryIndexedDb(
|
const indexedDbResults = await queryIndexedDb(
|
||||||
indexedDb,
|
indexedDb,
|
||||||
type,
|
type,
|
||||||
queryObj,
|
queryObj,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dbRecords = paginateResults(indexedDbResults, page, pageSize).map(
|
// Normalize the results and clean up indexedDb format, ready for ember data consumption
|
||||||
(item) =>
|
const normalizedResults = indexedDbResults.map((item) => {
|
||||||
this.indexedDb.normalizeData({
|
return this.indexedDb.normalizeData({
|
||||||
data: item,
|
data: item,
|
||||||
cleanData: false,
|
cleanData: false,
|
||||||
schema,
|
schema,
|
||||||
serializer,
|
serializer,
|
||||||
}),
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
// Return the raw data if we don't push to the store.
|
// Sort the results
|
||||||
let records = dbRecords.map((record) => ({
|
const sortedResults = sortResults(normalizedResults, {
|
||||||
...record.attributes,
|
querySort: queryObj?.sort,
|
||||||
id: record.id,
|
schema,
|
||||||
}));
|
});
|
||||||
|
|
||||||
if (pushToStore) {
|
// Paginate the results
|
||||||
records = store.push({ data: dbRecords });
|
const paginatedResults = paginateResults(sortedResults, page, pageSize);
|
||||||
}
|
|
||||||
|
// If we are not pushing to the store, use the raw data with id property
|
||||||
|
const records = pushToStore
|
||||||
|
? store.push({ data: paginatedResults })
|
||||||
|
: paginatedResults.map((record) => ({
|
||||||
|
...record.attributes,
|
||||||
|
id: record.id,
|
||||||
|
}));
|
||||||
|
|
||||||
// Set a meta property on the array to store the total items of the results.
|
// Set a meta property on the array to store the total items of the results.
|
||||||
// This isn't conventional but is better than returning an ArrayProxy
|
// This isn't conventional but is better than returning an ArrayProxy
|
||||||
@@ -169,4 +152,91 @@ export default class IndexedDbHandler {
|
|||||||
return next(context.request);
|
return next(context.request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async retryQueryFailure({
|
||||||
|
err,
|
||||||
|
indexedDb,
|
||||||
|
type,
|
||||||
|
adapter,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
remainingQuery,
|
||||||
|
}) {
|
||||||
|
// If we get an invalid list token, we'll delete the token and
|
||||||
|
// clear the DB and try again without a token.
|
||||||
|
if (
|
||||||
|
err?.errors[0].status === 400 &&
|
||||||
|
err?.errors[0].code === 'invalid list token'
|
||||||
|
) {
|
||||||
|
await indexedDb[type].clear();
|
||||||
|
// Delete all tokens of the same resource type so we don't keep clearing the DB.
|
||||||
|
// Even if other tokens may still be valid, we might have cleared data that they
|
||||||
|
// depended on so we should clear all tokens of the same type when clearing the DB.
|
||||||
|
const tokenKeys = await indexedDb.token
|
||||||
|
.where(':id')
|
||||||
|
.startsWith(type)
|
||||||
|
.primaryKeys();
|
||||||
|
|
||||||
|
await indexedDb.token.bulkDelete(tokenKeys);
|
||||||
|
|
||||||
|
return await adapter.query(store, schema, {
|
||||||
|
...remainingQuery,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeToIndexedDb({
|
||||||
|
payload,
|
||||||
|
storeToken,
|
||||||
|
indexedDb,
|
||||||
|
tokenKey,
|
||||||
|
type,
|
||||||
|
serializer,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
}) {
|
||||||
|
// Store the token we just got back from the payload if it exists
|
||||||
|
if (payload.list_token && storeToken) {
|
||||||
|
await indexedDb.token.put({
|
||||||
|
id: tokenKey,
|
||||||
|
token: payload.list_token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any records from the DB if the API indicates they've been deleted
|
||||||
|
if (payload.removed_ids?.length > 0) {
|
||||||
|
await indexedDb[type].bulkDelete(payload.removed_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a temporary fix of clearing the DB (specifically for auth-methods)
|
||||||
|
// since we are not storing the token we do not get back a list of removed_ids
|
||||||
|
// from the API call and we do not want deleted items from showing in list view.
|
||||||
|
if (!storeToken) {
|
||||||
|
await indexedDb[type].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPayload = serializer.normalizeResponse(
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
payload,
|
||||||
|
null,
|
||||||
|
'query',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: payloadData } = normalizedPayload;
|
||||||
|
|
||||||
|
// Store the new data we just got back from the API refresh
|
||||||
|
const items = payloadData.map((datum) =>
|
||||||
|
this.indexedDb.normalizeData({
|
||||||
|
data: datum,
|
||||||
|
cleanData: true,
|
||||||
|
schema,
|
||||||
|
serializer,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await indexedDb[type].bulkPut(items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import Mixin from '@ember/object/mixin';
|
import Mixin from '@ember/object/mixin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides URL-building methods in an adpater for greater flexibility.
|
* Overrides URL-building methods in an adapter for greater flexibility.
|
||||||
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
|
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
|
||||||
* `snapshot`, which aren't normally passed. And adds support for a
|
* `snapshot`, which aren't normally passed. And adds support for a
|
||||||
* `urlSuffix` method, the return value of which is appended to the end of
|
* `urlSuffix` method, the return value of which is appended to the end of
|
||||||
@@ -22,7 +22,7 @@ export default Mixin.create({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This override behaves identically to the default method by default.
|
* This override behaves identically to the default method by default.
|
||||||
* It exists only to document the additional paramters to the method.
|
* It exists only to document the additional parameters to the method.
|
||||||
* @override
|
* @override
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @param {string} parentURL
|
* @param {string} parentURL
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export default class AccountModel extends GeneratedAccountModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get accountName() {
|
get accountName() {
|
||||||
const { email, full_name, login_name } = this;
|
const { email, full_name, login_name, subject } = this;
|
||||||
return email || full_name || login_name;
|
return email || full_name || login_name || subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import Model from '@ember-data/model';
|
import Model from '@ember-data/model';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base model class provides common functionality for all models.
|
* Base model class provides common functionality for all models.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import GeneratedGroupModel from '../generated/models/group';
|
|||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export default class GroupModel extends GeneratedGroupModel {
|
export default class GroupModel extends GeneratedGroupModel {
|
||||||
// =services
|
// =services
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
|
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export const TYPE_HOST_CATALOG_STATIC = 'static';
|
export const TYPE_HOST_CATALOG_STATIC = 'static';
|
||||||
export const TYPE_HOST_CATALOG_DYNAMIC = 'plugin';
|
export const TYPE_HOST_CATALOG_DYNAMIC = 'plugin';
|
||||||
@@ -40,7 +41,8 @@ export const STATIC_CREDENTIAL_FIELDS = ['access_key_id, secret_access_key'];
|
|||||||
export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
||||||
// =attributes
|
// =attributes
|
||||||
|
|
||||||
#credentialType;
|
@tracked _credentialTypeValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the host catalog is static.
|
* True if the host catalog is static.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -62,14 +64,14 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get credentialType() {
|
get credentialType() {
|
||||||
if (!this.#credentialType) {
|
if (!this._credentialTypeValue) {
|
||||||
if (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) {
|
if (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
|
return TYPE_CREDENTIAL_DYNAMIC;
|
||||||
} else {
|
} else {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_STATIC;
|
return TYPE_CREDENTIAL_STATIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.#credentialType;
|
return this._credentialTypeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +79,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
set credentialType(type) {
|
set credentialType(type) {
|
||||||
this.#credentialType = type;
|
this._credentialTypeValue = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedRoleModel from '../generated/models/role';
|
import GeneratedRoleModel from '../generated/models/role';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const GRANT_SCOPE_THIS = 'this';
|
export const GRANT_SCOPE_THIS = 'this';
|
||||||
export const GRANT_SCOPE_CHILDREN = 'children';
|
export const GRANT_SCOPE_CHILDREN = 'children';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedSessionRecordingModel from '../generated/models/session-recording';
|
import GeneratedSessionRecordingModel from '../generated/models/session-recording';
|
||||||
import { hasMany } from '@ember-data/model';
|
import { hasMany } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const TYPE_SESSION_RECORDING_SSH = 'ssh';
|
export const TYPE_SESSION_RECORDING_SSH = 'ssh';
|
||||||
export const TYPES_SESSION_RECORDING = Object.freeze([
|
export const TYPES_SESSION_RECORDING = Object.freeze([
|
||||||
@@ -71,7 +71,15 @@ export default class SessionRecordingModel extends GeneratedSessionRecordingMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reapply policy dates via the `reapply-staorage-policy` method.
|
* Returns true if retention of session-recording is forever.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get retainForever() {
|
||||||
|
return this.retain_until?.toISOString() === '9999-12-31T23:23:23.999Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reapply policy dates via the `reapply-storage-policy` method.
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @param {object} options.adapterOptions
|
* @param {object} options.adapterOptions
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import GeneratedSessionModel from '../generated/models/session';
|
|||||||
import { equal } from '@ember/object/computed';
|
import { equal } from '@ember/object/computed';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { flattenObject } from '../utils/flatten-nested-object';
|
import { flattenObject } from '../utils/flatten-nested-object';
|
||||||
import { TYPES_CREDENTIAL_LIBRARY } from 'api/models/credential-library';
|
import { TYPES_CREDENTIAL_LIBRARY } from 'api/models/credential-library';
|
||||||
import { TYPE_CREDENTIAL_JSON } from 'api/models/credential';
|
import { TYPE_CREDENTIAL_JSON } from 'api/models/credential';
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import GeneratedStorageBucketModel from '../generated/models/storage-bucket';
|
import GeneratedStorageBucketModel from '../generated/models/storage-bucket';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
|
export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
|
||||||
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
|
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
|
||||||
@@ -26,7 +27,8 @@ export const TYPES_CREDENTIALS = Object.freeze([
|
|||||||
export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
||||||
// attributes
|
// attributes
|
||||||
|
|
||||||
#credentialType;
|
@tracked _credentialTypeValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the storage bucket is a plugin.
|
* True if the storage bucket is a plugin.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -40,14 +42,14 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get credentialType() {
|
get credentialType() {
|
||||||
if (!this.#credentialType) {
|
if (!this._credentialTypeValue) {
|
||||||
if (this.role_arn) {
|
if (this.role_arn) {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
|
return TYPE_CREDENTIAL_DYNAMIC;
|
||||||
} else {
|
} else {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_STATIC;
|
return TYPE_CREDENTIAL_STATIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.#credentialType;
|
return this._credentialTypeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,8 +57,9 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
set credentialType(type) {
|
set credentialType(type) {
|
||||||
this.#credentialType = type;
|
this._credentialTypeValue = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the storage bucket is an unknown type.
|
* True if the storage bucket is an unknown type.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedTargetModel from '../generated/models/target';
|
import GeneratedTargetModel from '../generated/models/target';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const TYPE_TARGET_TCP = 'tcp';
|
export const TYPE_TARGET_TCP = 'tcp';
|
||||||
export const TYPE_TARGET_SSH = 'ssh';
|
export const TYPE_TARGET_SSH = 'ssh';
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
// Do not serialize `disabled` fields.
|
// Do not serialize `disabled` fields.
|
||||||
// TODO: disabled is temporarily disabled
|
// TODO: disabled is temporarily disabled
|
||||||
if (key === 'disabled') delete json[key];
|
if (key === 'disabled') delete json[key];
|
||||||
|
|
||||||
|
// Before we transform our json
|
||||||
|
// Let's trim the values in the json obj
|
||||||
|
// Currently, json values will only ever be a string or an array
|
||||||
|
if (options.trimWhitespace && json[key]) {
|
||||||
|
if (typeOf(json[key]) === 'array') {
|
||||||
|
json[key] = json[key].map((str) => str.trim());
|
||||||
|
} else if (typeOf(json[key] === 'string')) {
|
||||||
|
json[key] = json[key].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Push nested attributes down into the attributes key
|
// Push nested attributes down into the attributes key
|
||||||
if (options.isNestedAttribute && json[key] !== undefined) {
|
if (options.isNestedAttribute && json[key] !== undefined) {
|
||||||
if (!json.attributes) json.attributes = {};
|
if (!json.attributes) json.attributes = {};
|
||||||
@@ -232,12 +244,10 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
normalizeMissingArrays(store, primaryModelClass, payload) {
|
normalizeMissingArrays(store, primaryModelClass, payload) {
|
||||||
const attrDefs = store
|
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
|
||||||
.getSchemaDefinitionService()
|
|
||||||
.attributesDefinitionFor({ type: primaryModelClass.modelName });
|
|
||||||
if (attrDefs) {
|
if (attrDefs) {
|
||||||
Object.keys(attrDefs).forEach((key) => {
|
attrDefs.keys().forEach((key) => {
|
||||||
if (!payload[key] && attrDefs[key]?.options?.emptyArrayIfMissing) {
|
if (!payload[key] && attrDefs.get(key)?.options?.emptyArrayIfMissing) {
|
||||||
payload[key] = [];
|
payload[key] = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -256,12 +266,10 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
normalizeMissingObjects(store, primaryModelClass, payload) {
|
normalizeMissingObjects(store, primaryModelClass, payload) {
|
||||||
const attrDefs = store
|
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
|
||||||
.getSchemaDefinitionService()
|
|
||||||
.attributesDefinitionFor({ type: primaryModelClass.modelName });
|
|
||||||
if (attrDefs) {
|
if (attrDefs) {
|
||||||
Object.keys(attrDefs).forEach((key) => {
|
attrDefs.keys().forEach((key) => {
|
||||||
if (!payload[key] && attrDefs[key]?.options?.emptyObjectIfMissing) {
|
if (!payload[key] && attrDefs.get(key)?.options?.emptyObjectIfMissing) {
|
||||||
payload[key] = {};
|
payload[key] = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
|||||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
|
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
|
||||||
return this.serializeVaultGeneric(...arguments);
|
return this.serializeVaultGeneric(...arguments);
|
||||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
|
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
|
||||||
|
return this.serializeVaultSshCertificate(...arguments);
|
||||||
default:
|
default:
|
||||||
return super.serialize(...arguments);
|
return super.serialize(...arguments);
|
||||||
}
|
}
|
||||||
@@ -61,4 +62,16 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
|||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializeVaultSshCertificate() {
|
||||||
|
const serialized = super.serialize(...arguments);
|
||||||
|
if (serialized.attributes) {
|
||||||
|
const { key_type } = serialized.attributes;
|
||||||
|
if (key_type !== 'rsa' && key_type !== 'ecdsa') {
|
||||||
|
// If the key type is not RSA or ECDSA, set key_bits to null
|
||||||
|
serialized.attributes.key_bits = null;
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
|
|||||||
) {
|
) {
|
||||||
if (options.isNestedAttribute) {
|
if (options.isNestedAttribute) {
|
||||||
json.attributes[key] = null;
|
json.attributes[key] = null;
|
||||||
} else if (options.isNestedSecret && json.secrets[key]) {
|
} else if (options.isNestedSecret && json.secrets?.[key]) {
|
||||||
delete json.secrets[key];
|
delete json.secrets[key];
|
||||||
if (Object.keys(json.secrets).length === 0) {
|
if (Object.keys(json.secrets).length === 0) {
|
||||||
delete json.secrets;
|
delete json.secrets;
|
||||||
|
|||||||
@@ -95,4 +95,18 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
|||||||
|
|
||||||
return super.normalize(typeClass, normalizedHash, ...rest);
|
return super.normalize(typeClass, normalizedHash, ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts errors from the payload and transforms specific keys if needed.
|
||||||
|
* @override
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
extractErrors() {
|
||||||
|
const errors = super.extractErrors(...arguments);
|
||||||
|
if (errors?.filter) {
|
||||||
|
errors.filter_string = errors.filter;
|
||||||
|
delete errors.filter;
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -53,4 +53,18 @@ export default class ManagedGroupSerializer extends ApplicationSerializer {
|
|||||||
|
|
||||||
return super.normalize(typeClass, normalizedHash, ...rest);
|
return super.normalize(typeClass, normalizedHash, ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts errors from the payload and transforms specific keys if needed.
|
||||||
|
* @override
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
extractErrors() {
|
||||||
|
const errors = super.extractErrors(...arguments);
|
||||||
|
if (errors?.filter) {
|
||||||
|
errors.filter_string = errors.filter;
|
||||||
|
delete errors.filter;
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,7 @@ export default class StorageBucketSerializer extends ApplicationSerializer {
|
|||||||
const value = super.serializeAttribute(...arguments);
|
const value = super.serializeAttribute(...arguments);
|
||||||
const { credentialType } = snapshot.record;
|
const { credentialType } = snapshot.record;
|
||||||
const { options } = attribute;
|
const { options } = attribute;
|
||||||
|
|
||||||
// This deletes any fields that don't belong to the record's credential type
|
// This deletes any fields that don't belong to the record's credential type
|
||||||
if (options.isNestedAttribute && json.attributes) {
|
if (options.isNestedAttribute && json.attributes) {
|
||||||
// The key must be included in the fieldsByType list above
|
// The key must be included in the fieldsByType list above
|
||||||
|
|||||||
@@ -97,23 +97,42 @@ const unconvertFields = (result, object, schema, serializer) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to encapsulate the IndexedDB implementation. To use this service, call
|
* Service to encapsulate the IndexedDB implementation. To use this service, call
|
||||||
* `setup` from the application root and
|
* `setup` from the application root. This will create the database and the necessary indexes.
|
||||||
|
*
|
||||||
|
* Make sure to increment the version number of the database whenever the indexes change.
|
||||||
|
* This will delete the old database and create a new one with the new indexes.
|
||||||
*/
|
*/
|
||||||
export default class IndexedDbService extends Service {
|
export default class IndexedDbService extends Service {
|
||||||
// =attributes
|
// =attributes
|
||||||
#db;
|
#db;
|
||||||
|
#version = 2;
|
||||||
|
|
||||||
get db() {
|
get db() {
|
||||||
return this.#db;
|
return this.#db;
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(dbName) {
|
async setup(dbName) {
|
||||||
// Don't run setup again if we already have one or if we didn't get a name
|
// Don't run setup again if we already have one or if we didn't get a name
|
||||||
if (this.#db || !dbName) {
|
if (this.#db || !dbName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const doesDbExist = await Dexie.exists(dbName);
|
||||||
|
|
||||||
|
// If the database already exists, open the database and
|
||||||
|
// check if the version is a lower version than the current one
|
||||||
|
if (doesDbExist) {
|
||||||
|
const dbVerifier = new Dexie(dbName);
|
||||||
|
await dbVerifier.open();
|
||||||
|
if (dbVerifier.verno < this.#version) {
|
||||||
|
await dbVerifier.delete();
|
||||||
|
} else {
|
||||||
|
dbVerifier.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.#db = new Dexie(dbName);
|
this.#db = new Dexie(dbName);
|
||||||
this.#db.version(2).stores(modelIndexes);
|
this.#db.version(this.#version).stores(modelIndexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Service from '@ember/service';
|
import Service from '@ember/service';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a POJO representing a query and converts it to a boolean expression
|
* Takes a POJO representing a query and converts it to a boolean expression
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class MemoryStorage {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* import Route from '@ember/routing/route';
|
* import Route from '@ember/routing/route';
|
||||||
* import { inject as service } from '@ember/service';
|
* import { service } from '@ember/service';
|
||||||
* export default class ApplicationRoute extends Route {
|
* export default class ApplicationRoute extends Route {
|
||||||
* @service storage;
|
* @service storage;
|
||||||
* afterModel() {
|
* afterModel() {
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Store, { CacheHandler } from '@ember-data/store';
|
// Importing 'Store' from 'ember-data/store' to get default configuration automatically.
|
||||||
|
// TODO: Remove eslint-disable after upgrading to eslint-plugin-ember >= v12.2.1
|
||||||
|
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
|
||||||
|
import Store from 'ember-data/store';
|
||||||
|
import { CacheHandler } from '@ember-data/store';
|
||||||
import RequestManager from '@ember-data/request';
|
import RequestManager from '@ember-data/request';
|
||||||
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
||||||
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class AccountValueMapArrayTransform {
|
||||||
|
|
||||||
export default class AccountValueMapArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const accountValues = serialized || [];
|
const accountValues = serialized || [];
|
||||||
return accountValues.map((accountValueString) => ({
|
return accountValues.map((accountValueString) => ({
|
||||||
@@ -18,4 +16,8 @@ export default class AccountValueMapArrayTransform extends Transform {
|
|||||||
const accountValues = deserialized || [];
|
const accountValues = deserialized || [];
|
||||||
return accountValues.map(({ key, value }) => `${key}=${value}`);
|
return accountValues.map(({ key, value }) => `${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
|
||||||
import { TrackedArray } from 'tracked-built-ins';
|
import { TrackedArray } from 'tracked-built-ins';
|
||||||
|
|
||||||
export default class ArrayTransform extends Transform {
|
export default class ArrayTransform {
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const arr = new TrackedArray(serialized || []);
|
const arr = new TrackedArray(serialized || []);
|
||||||
return arr;
|
return arr;
|
||||||
@@ -16,4 +15,8 @@ export default class ArrayTransform extends Transform {
|
|||||||
const arr = deserialized || [];
|
const arr = deserialized || [];
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class DurationTransform {
|
||||||
|
|
||||||
export default class DurationTransform extends Transform {
|
|
||||||
/**
|
/**
|
||||||
* Normalize a duration string in the format "3.0001s" to milliseconds.
|
* Normalize a duration string in the format "3.0001s" to milliseconds.
|
||||||
* The backend returns a precision of nanoseconds but we round to whole
|
* The backend returns a precision of nanoseconds but we round to whole
|
||||||
@@ -37,4 +35,8 @@ export default class DurationTransform extends Transform {
|
|||||||
maximumFractionDigits: 9,
|
maximumFractionDigits: 9,
|
||||||
})}s`;
|
})}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class HostSourceIdArrayTransform {
|
||||||
|
|
||||||
export default class HostSourceIdArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const stringValues = serialized || [];
|
const stringValues = serialized || [];
|
||||||
return stringValues.map(({ id: host_source_id, ...obj }) => ({
|
return stringValues.map(({ id: host_source_id, ...obj }) => ({
|
||||||
@@ -18,4 +16,8 @@ export default class HostSourceIdArrayTransform extends Transform {
|
|||||||
const strings = deserialized || [];
|
const strings = deserialized || [];
|
||||||
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
|
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class ObjectAsArrayTransform {
|
||||||
|
|
||||||
export default class ObjectAsArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const obj = serialized || {};
|
const obj = serialized || {};
|
||||||
return Object.entries(obj).map(([key, value]) => ({ key, value }));
|
return Object.entries(obj).map(([key, value]) => ({ key, value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(deserialized) {
|
serialize(deserialized) {
|
||||||
const array = deserialized || [];
|
const array = deserialized || [];
|
||||||
|
|
||||||
@@ -24,4 +23,8 @@ export default class ObjectAsArrayTransform extends Transform {
|
|||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
|
||||||
import { TrackedObject } from 'tracked-built-ins';
|
import { TrackedObject } from 'tracked-built-ins';
|
||||||
|
|
||||||
export default class ObjectTransform extends Transform {
|
export default class ObjectTransform {
|
||||||
/**
|
/**
|
||||||
* @param serialized
|
* @param serialized
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
@@ -26,4 +25,8 @@ export default class ObjectTransform extends Transform {
|
|||||||
}
|
}
|
||||||
return deserialized;
|
return deserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class StringArrayTransform {
|
||||||
|
|
||||||
export default class StringArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const stringValues = serialized || [];
|
const stringValues = serialized || [];
|
||||||
return stringValues.map((value) => ({ value }));
|
return stringValues.map((value) => ({ value }));
|
||||||
@@ -15,4 +13,8 @@ export default class StringArrayTransform extends Transform {
|
|||||||
const strings = deserialized || [];
|
const strings = deserialized || [];
|
||||||
return strings.map(({ value }) => value);
|
return strings.map(({ value }) => value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -70,14 +70,9 @@ export async function queryIndexedDb(indexedDb, resource, query) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderBy has to be used on where clauses while if it's a
|
// filterCollection can be Dexie Table or a Dexie Collection.
|
||||||
// collection already we have to use sortBy. We'll also sort by
|
// This is an important detail you need to keep in mind when working on it.
|
||||||
// descending order based on created time to match the API as any
|
return filterCollection.toArray();
|
||||||
// "or" clause can change the order
|
|
||||||
if (filterCollection.orderBy) {
|
|
||||||
return filterCollection.orderBy(getKey('created_time')).reverse().toArray();
|
|
||||||
}
|
|
||||||
return filterCollection.reverse().sortBy(getKey('created_time'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildInitialWhereClause = ({ filterArrayOrObject, table, key }) => {
|
const buildInitialWhereClause = ({ filterArrayOrObject, table, key }) => {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// created_time is not an attribute defined on all models, but is present in all the resources
|
||||||
|
// coming from API
|
||||||
|
const SORT_DEFAULT_ATTRIBUTE = 'created_time';
|
||||||
|
const SORT_DIRECTION_ASCENDING = 'asc';
|
||||||
|
const SORT_DIRECTION_DESCENDING = 'desc';
|
||||||
|
|
||||||
|
const sortFunctions = {
|
||||||
|
string: (a, b) => String(a).localeCompare(String(b)),
|
||||||
|
date: (a, b) => new Date(a).getTime() - new Date(b).getTime(),
|
||||||
|
number: (a, b) => Number(a) - Number(b),
|
||||||
|
boolean: (a, b) => Number(a) - Number(b),
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSortableValue(schema, record, attribute) {
|
||||||
|
if (attribute === 'id') {
|
||||||
|
return record.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all models have a created_time attribute but not all models have it defined
|
||||||
|
if (attribute === 'created_time') {
|
||||||
|
return record.attributes.created_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.attributes.has(attribute)) {
|
||||||
|
return record.attributes[attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = Array.from(schema.attributes.keys());
|
||||||
|
throw new Error(
|
||||||
|
`The attribute "${attribute}" does not map to the model definition of type "${record.type}". Supported sortable attributes are 'id', 'created_time', or ${attributes.map((attr) => `'${attr}'`).join(', ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sortResults = (results, { querySort, schema }) => {
|
||||||
|
querySort = querySort ?? {};
|
||||||
|
const sortAttribute = querySort.attribute || SORT_DEFAULT_ATTRIBUTE;
|
||||||
|
|
||||||
|
// Default sort direction is ascending unless we are sorting by `created_time` (default sort attribute)
|
||||||
|
const defaultSortDirection =
|
||||||
|
sortAttribute === SORT_DEFAULT_ATTRIBUTE && !querySort.sortFunction
|
||||||
|
? SORT_DIRECTION_DESCENDING
|
||||||
|
: SORT_DIRECTION_ASCENDING;
|
||||||
|
const sortDirection = querySort.direction || defaultSortDirection;
|
||||||
|
|
||||||
|
if (
|
||||||
|
![SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING].includes(
|
||||||
|
sortDirection,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error('Invalid sort direction');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute custom sort function if one is provided.
|
||||||
|
if (querySort.sortFunction) {
|
||||||
|
return results.toSorted((a, b) => {
|
||||||
|
const sortResult = querySort.sortFunction(a, b);
|
||||||
|
|
||||||
|
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||||
|
? sortResult
|
||||||
|
: -1 * sortResult;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortAttributeDataType = schema.attributes.get(sortAttribute)?.type;
|
||||||
|
|
||||||
|
const sortFunction =
|
||||||
|
sortFunctions[sortAttributeDataType] ?? sortFunctions.string;
|
||||||
|
|
||||||
|
return results.toSorted((a, b) => {
|
||||||
|
// Extract the values to sort and sort them.
|
||||||
|
const sortValueA = getSortableValue(schema, a, sortAttribute);
|
||||||
|
const sortValueB = getSortableValue(schema, b, sortAttribute);
|
||||||
|
const sortResult = sortFunction(sortValueA, sortValueB);
|
||||||
|
|
||||||
|
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||||
|
? sortResult
|
||||||
|
: -1 * sortResult;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ember-data no longer automatically installs BooleanTransform for apps.
|
||||||
|
export { BooleanTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default } from 'rose/components/rose/dialog/body';
|
export { DateTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default, initialize } from 'core/initializers/deprecations';
|
export { NumberTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ember-data no longer automatically installs StringTransform for apps.
|
||||||
|
export { StringTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
|
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
|
||||||
const { maybeEmbroider } = require('@embroider/test-setup');
|
|
||||||
|
|
||||||
module.exports = function (defaults) {
|
module.exports = function (defaults) {
|
||||||
const app = new EmberAddon(defaults, {
|
const app = new EmberAddon(defaults, {
|
||||||
@@ -19,6 +18,8 @@ module.exports = function (defaults) {
|
|||||||
This build file does *not* influence how the addon or the app using it
|
This build file does *not* influence how the addon or the app using it
|
||||||
behave. You most likely want to be modifying `./index.js` or app's build file
|
behave. You most likely want to be modifying `./index.js` or app's build file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const { maybeEmbroider } = require('@embroider/test-setup');
|
||||||
return maybeEmbroider(app, {
|
return maybeEmbroider(app, {
|
||||||
skipBabel: [
|
skipBabel: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export default factory.extend({
|
|||||||
}
|
}
|
||||||
case TYPE_AUTH_METHOD_OIDC:
|
case TYPE_AUTH_METHOD_OIDC:
|
||||||
return {
|
return {
|
||||||
issuer: faker.internet.ip(),
|
issuer:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
subject: 'sub',
|
subject: 'sub',
|
||||||
email: faker.internet.email(),
|
email: faker.internet.email(),
|
||||||
full_name: faker.person.fullName(),
|
full_name: faker.person.fullName(),
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export default factory.extend({
|
|||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case 'vault':
|
case 'vault':
|
||||||
return {
|
return {
|
||||||
address: faker.internet.ip(),
|
address:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
namespace: faker.word.words(1),
|
namespace: faker.word.words(1),
|
||||||
ca_cert: 'ca-cert-123',
|
ca_cert: 'ca-cert-123',
|
||||||
tls_server_name: faker.internet.domainName(),
|
tls_server_name: faker.internet.domainName(),
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ const pluginTypes = [
|
|||||||
'foobar',
|
'foobar',
|
||||||
];
|
];
|
||||||
|
|
||||||
let pluginTypeCounter = 1;
|
|
||||||
|
|
||||||
export default factory.extend({
|
export default factory.extend({
|
||||||
id: () => generateId('hc_'),
|
id: () => generateId('hc_'),
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ export default factory.extend({
|
|||||||
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
||||||
return {
|
return {
|
||||||
id: `plugin-id-${i}`,
|
id: `plugin-id-${i}`,
|
||||||
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
|
name: pluginTypes[i % pluginTypes.length],
|
||||||
description: faker.word.words(),
|
description: faker.word.words(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,12 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
preferred_endpoints() {
|
preferred_endpoints() {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === 'plugin') {
|
||||||
const enpointsAmount = faker.number.int({ min: 1, max: 5 });
|
const endpointsAmount = faker.number.int({ min: 1, max: 5 });
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let i = 0; i < enpointsAmount; ++i) {
|
for (let i = 0; i < endpointsAmount; ++i) {
|
||||||
result.push(faker.internet.ip());
|
result.push(
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ export default factory.extend({
|
|||||||
const addressesAmount = Math.floor(Math.random() * 6) + 1;
|
const addressesAmount = Math.floor(Math.random() * 6) + 1;
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let i = 0; i < addressesAmount; ++i) {
|
for (let i = 0; i < addressesAmount; ++i) {
|
||||||
result.push(faker.internet.ip());
|
result.push(
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -103,6 +103,31 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
withManyHosts: trait({
|
||||||
|
afterCreate(target, server) {
|
||||||
|
const { scope } = target;
|
||||||
|
const hostCatalog = server.create('host-catalog', { scope });
|
||||||
|
const hosts = server.createList('host', 15, {
|
||||||
|
scope,
|
||||||
|
hostCatalog,
|
||||||
|
type: hostCatalog.type,
|
||||||
|
});
|
||||||
|
const hostSets = [
|
||||||
|
server.create('host-set', {
|
||||||
|
scope,
|
||||||
|
hostCatalog,
|
||||||
|
hosts,
|
||||||
|
type: hostCatalog.type,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
|
target.update({ hostSets });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
withTwoHosts: trait({
|
withTwoHosts: trait({
|
||||||
afterCreate(target, server) {
|
afterCreate(target, server) {
|
||||||
const { scope } = target;
|
const { scope } = target;
|
||||||
@@ -120,6 +145,10 @@ export default factory.extend({
|
|||||||
type: hostCatalog.type,
|
type: hostCatalog.type,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
target.update({ hostSets });
|
target.update({ hostSets });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -139,6 +168,10 @@ export default factory.extend({
|
|||||||
type: hostCatalog.type,
|
type: hostCatalog.type,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
target.update({ hostSets });
|
target.update({ hostSets });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default Factory.extend({
|
|||||||
updated_time: () => faker.date.recent(),
|
updated_time: () => faker.date.recent(),
|
||||||
version: () => faker.number.int(),
|
version: () => faker.number.int(),
|
||||||
attributes: () => {
|
attributes: () => {
|
||||||
return { address: faker.internet.ipv6() };
|
return {
|
||||||
|
address:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -3,30 +3,8 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// import config from '../../config/environment';
|
|
||||||
import { Response } from 'miragejs';
|
import { Response } from 'miragejs';
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Simulates a cookie-based response to authentication requests. Any user/pass
|
|
||||||
// * values are allowed *except* when `username` equals `error`, in which case a
|
|
||||||
// * simulated error response is returned.
|
|
||||||
// * @param {object} schema
|
|
||||||
// * @param {object} request
|
|
||||||
// * @return {Response}
|
|
||||||
// */
|
|
||||||
// export default function authenticateHandler(schema, request) {
|
|
||||||
// const payload = JSON.parse(request.requestBody);
|
|
||||||
// if (payload.attributes.login_name === 'error') {
|
|
||||||
// return new Response(400);
|
|
||||||
// } else {
|
|
||||||
// const cookieName = config.auth.passwordCookieName;
|
|
||||||
// const cookieValue = v1();
|
|
||||||
// const cookiePath = config.rootURL;
|
|
||||||
// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`;
|
|
||||||
// return new Response(200);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Quick and dirty counter system to simulate the polling behavior
|
// Quick and dirty counter system to simulate the polling behavior
|
||||||
// of the API. If the OIDC flow is pending, the route returns 100.
|
// of the API. If the OIDC flow is pending, the route returns 100.
|
||||||
// If complete it returns 200 with the token JSON.
|
// If complete it returns 200 with the token JSON.
|
||||||
|
|||||||
@@ -110,7 +110,18 @@ export default function (server) {
|
|||||||
|
|
||||||
// Other resources
|
// Other resources
|
||||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||||
server.createList('host-catalog', 8, { scope }, 'withChildren');
|
server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
2,
|
||||||
|
{ scope, type: 'static' },
|
||||||
|
'withChildren',
|
||||||
|
);
|
||||||
|
server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
8,
|
||||||
|
{ scope, type: 'plugin' },
|
||||||
|
'withChildren',
|
||||||
|
);
|
||||||
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
||||||
server.createList('target', 2, { scope }, 'withAssociations');
|
server.createList('target', 2, { scope }, 'withAssociations');
|
||||||
server.create('target', { scope, address: '0.0.0.0' });
|
server.create('target', { scope, address: '0.0.0.0' });
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ export default function initializeMockIPC(server, config) {
|
|||||||
* Check for OS chrome state
|
* Check for OS chrome state
|
||||||
*/
|
*/
|
||||||
showWindowActions() {
|
showWindowActions() {
|
||||||
|
if (navigator.userAgent.includes('Mac')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-28
@@ -10,8 +10,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:development": "ember build",
|
"build:development": "ember build",
|
||||||
"build": "ember build --environment=production",
|
"build": "ember build --environment=production",
|
||||||
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
|
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"",
|
||||||
"lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
|
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\"",
|
||||||
|
"lint:hbs": "ember-template-lint .",
|
||||||
|
"lint:hbs:fix": "ember-template-lint . --fix",
|
||||||
"lint:js": "eslint .",
|
"lint:js": "eslint .",
|
||||||
"lint:js:fix": "eslint . --fix",
|
"lint:js:fix": "eslint . --fix",
|
||||||
"format": "concurrently \"npm:format:*\" --names \"format:\"",
|
"format": "concurrently \"npm:format:*\" --names \"format:\"",
|
||||||
@@ -19,61 +21,69 @@
|
|||||||
"start": "ember serve",
|
"start": "ember serve",
|
||||||
"test": "ember exam --test-port 0 --split=4 --parallel=1 --random",
|
"test": "ember exam --test-port 0 --split=4 --parallel=1 --random",
|
||||||
"test:ember-compatibility": "ember try:each",
|
"test:ember-compatibility": "ember try:each",
|
||||||
"precommit": "yarn lint && yarn format && yarn test",
|
"precommit": "pnpm lint && pnpm format && pnpm test",
|
||||||
"doc:toc": "doctoc README.md"
|
"doc:toc": "doctoc README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.26.10",
|
||||||
|
"@ember/string": "^4.0.0",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"ember-auto-import": "^2.8.1",
|
"ember-auto-import": "^2.10.0",
|
||||||
"ember-can": "^4.2.0",
|
"ember-can": "^4.2.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-cli-mirage": "^3.0.3",
|
"ember-cli-mirage": "^3.0.3",
|
||||||
|
"ember-data": "~5.3.12",
|
||||||
"miragejs": "^0.1.48",
|
"miragejs": "^0.1.48",
|
||||||
"ember-data": "~4.12.0",
|
|
||||||
"tracked-built-ins": "^3.1.1"
|
"tracked-built-ins": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/eslint-parser": "^7.25.1",
|
||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/test-helpers": "^5.2.1",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/test-waiters": "^4.1.0",
|
||||||
"@ember/test-helpers": "^4.0.4",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@embroider/test-setup": "^2.1.1",
|
|
||||||
"@faker-js/faker": "^8.0.2",
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.0",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^5.12.0",
|
"ember-cli": "~5.12.0",
|
||||||
|
"ember-cli-clean-css": "^3.0.0",
|
||||||
"ember-cli-dependency-checker": "^3.3.2",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-exam": "^8.0.0",
|
"ember-exam": "^8.0.0",
|
||||||
"ember-load-initializers": "^2.1.2",
|
"ember-load-initializers": "^2.1.2",
|
||||||
"ember-page-title": "^7.0.0",
|
"ember-page-title": "^8.2.3",
|
||||||
"ember-qunit": "^8.1.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^12.0.1",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~5.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"ember-template-lint": "^5.7.2",
|
"ember-template-lint": "^6.0.0",
|
||||||
"eslint": "^8.54.0",
|
"ember-try": "^3.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-plugin-ember": "^11.11.1",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-n": "^16.3.1",
|
"eslint-plugin-ember": "^12.2.1",
|
||||||
"eslint-plugin-qunit": "^8.0.1",
|
"eslint-plugin-n": "^16.6.2",
|
||||||
|
"eslint-plugin-qunit": "^8.1.2",
|
||||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.3.3",
|
||||||
"qunit": "^2.22.0",
|
"qunit": "^2.22.0",
|
||||||
"qunit-dom": "^3.2.1",
|
"qunit-dom": "^3.2.1",
|
||||||
"webpack": "^5.94.0"
|
"sinon": "^19.0.2",
|
||||||
|
"webpack": "^5.95.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ember-source": ">= 4.0.0",
|
||||||
|
"sinon": "^19.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.* || 20.*"
|
"node": "20.* || 22.*"
|
||||||
},
|
},
|
||||||
"ember": {
|
"ember": {
|
||||||
"edition": "octane"
|
"edition": "octane"
|
||||||
@@ -82,8 +92,11 @@
|
|||||||
"configPath": "tests/dummy/config"
|
"configPath": "tests/dummy/config"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": "eslint --fix",
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
"*.hbs": "ember-template-lint --fix",
|
"*.hbs": "ember-template-lint --fix",
|
||||||
"*.{js,scss,yaml}": "prettier --write"
|
"*.{scss,yaml}": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,12 @@ module.exports = {
|
|||||||
// --no-sandbox is needed when running Chrome inside a container
|
// --no-sandbox is needed when running Chrome inside a container
|
||||||
process.env.CI ? '--no-sandbox' : null,
|
process.env.CI ? '--no-sandbox' : null,
|
||||||
'--headless',
|
'--headless',
|
||||||
|
// Disable backgrounding renders for occluded windows
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
// Disable renderer process backgrounding
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
// Disable task throttling of timer tasks from background pages
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
'--disable-dev-shm-usage',
|
'--disable-dev-shm-usage',
|
||||||
'--disable-gpu',
|
'--disable-gpu',
|
||||||
//'--disable-software-rasterizer',
|
//'--disable-software-rasterizer',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export default class ApplicationRoute extends Route {
|
export default class ApplicationRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
|
|||||||
@@ -2,4 +2,3 @@
|
|||||||
* Copyright (c) HashiCorp, Inc.
|
* Copyright (c) HashiCorp, Inc.
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
{{page-title "Dummy"}}
|
{{page-title "Dummy"}}
|
||||||
|
|
||||||
<h2 id="title">Welcome to Ember</h2>
|
<Hds::Text::Display @tag="h2" id="title">Welcome to Ember</Hds::Text::Display>
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "ember-cli",
|
"name": "ember-cli",
|
||||||
"version": "4.12.1",
|
"version": "5.12.0",
|
||||||
"blueprints": [
|
"blueprints": [
|
||||||
{
|
{
|
||||||
"name": "addon",
|
"name": "addon",
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"codemodsSource": "ember-addon-codemods-manifest@1",
|
"codemodsSource": "ember-addon-codemods-manifest@1",
|
||||||
"isBaseBlueprint": true,
|
"isBaseBlueprint": true,
|
||||||
"options": [
|
"options": [
|
||||||
"--no-welcome"
|
"--no-welcome",
|
||||||
|
"--pnpm"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,28 +10,21 @@ const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
|
|||||||
|
|
||||||
module.exports = async function () {
|
module.exports = async function () {
|
||||||
return {
|
return {
|
||||||
|
usePnpm: true,
|
||||||
scenarios: [
|
scenarios: [
|
||||||
{
|
{
|
||||||
name: 'ember-lts-4.4',
|
name: 'ember-lts-4.12',
|
||||||
npm: {
|
npm: {
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'ember-source': '~4.4.0',
|
'ember-source': '~4.12.0',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ember-lts-4.8',
|
name: 'ember-lts-5.4',
|
||||||
npm: {
|
npm: {
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'ember-source': '~4.8.0',
|
'ember-source': '~5.4.0',
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ember-lts-4.4',
|
|
||||||
npm: {
|
|
||||||
devDependencies: {
|
|
||||||
'ember-source': '~4.4.0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
setupTest as upstreamSetupTest,
|
setupTest as upstreamSetupTest,
|
||||||
} from 'ember-qunit';
|
} from 'ember-qunit';
|
||||||
|
|
||||||
// This file exists to provide wrappers around ember-qunit's / ember-mocha's
|
// This file exists to provide wrappers around ember-qunit's
|
||||||
// test setup functions. This way, you can easily extend the setup that is
|
// test setup functions. This way, you can easily extend the setup that is
|
||||||
// needed per test type.
|
// needed per test type.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,487 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
||||||
|
import RequestManager from '@ember-data/request';
|
||||||
|
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import { assert } from '@ember/debug';
|
||||||
|
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||||
|
import TargetModel from 'api/models/target';
|
||||||
|
|
||||||
|
function createPaginatedResponseHandler(mirageRecords, { pageSize }) {
|
||||||
|
assert('pageSize is required', pageSize);
|
||||||
|
const pagesCount = mirageRecords.length / pageSize;
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (let i = 0; i < pagesCount; i++) {
|
||||||
|
const items = mirageRecords.slice(i * pageSize, (i + 1) * pageSize);
|
||||||
|
const isLastPage = i === pagesCount - 1;
|
||||||
|
const list_token = `list-token-${faker.string.nanoid({ min: 5, max: 5 })}`;
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
items,
|
||||||
|
list_token,
|
||||||
|
response_type: !isLastPage ? 'delta' : 'complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextResult(listToken) {
|
||||||
|
const index = results.findIndex(
|
||||||
|
({ list_token }) => list_token === listToken,
|
||||||
|
);
|
||||||
|
const nextResult = index === -1 ? null : results[index + 1];
|
||||||
|
return nextResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirage handler compatible with paginated responses
|
||||||
|
return function (_schema, request) {
|
||||||
|
const listToken = request.queryParams.list_token;
|
||||||
|
if (!listToken) {
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = nextResult(listToken);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return new Response(
|
||||||
|
404,
|
||||||
|
{},
|
||||||
|
`No results found for list token "${listToken}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
setupIndexedDb(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
let manager,
|
||||||
|
indexeddbHandler,
|
||||||
|
store,
|
||||||
|
applicationAdapter,
|
||||||
|
indexedbHandlerSpy,
|
||||||
|
applicationAdapterSpy;
|
||||||
|
hooks.beforeEach(async function setupIndexHandler() {
|
||||||
|
store = this.owner.lookup('service:store');
|
||||||
|
manager = new RequestManager();
|
||||||
|
indexeddbHandler = new IndexedDbHandler(store);
|
||||||
|
applicationAdapter = store.adapterFor('application');
|
||||||
|
|
||||||
|
// spy on all handler and application adapter methods
|
||||||
|
indexedbHandlerSpy = sinon.spy(indexeddbHandler);
|
||||||
|
applicationAdapterSpy = sinon.spy(applicationAdapter);
|
||||||
|
|
||||||
|
store.requestManager = manager;
|
||||||
|
manager.use([indexeddbHandler]);
|
||||||
|
});
|
||||||
|
|
||||||
|
module('fetching and batch loading', function (hooks) {
|
||||||
|
// `testBatchLimit` and `serverResultPageLimit` are intentionally difference
|
||||||
|
// so that multiple calls to the server are required to fetch a single batch,
|
||||||
|
// and these multiples can be asserted in the tests individually
|
||||||
|
const testBatchLimit = 10;
|
||||||
|
const serverResultPageLimit = 5;
|
||||||
|
let aliases, aliasResponseHandlerSpy;
|
||||||
|
|
||||||
|
hooks.beforeEach(function setupMirageData() {
|
||||||
|
indexeddbHandler.batchLimit = testBatchLimit;
|
||||||
|
|
||||||
|
aliases = this.server.createList('alias', 50);
|
||||||
|
|
||||||
|
const aliasResponseHandler = createPaginatedResponseHandler(
|
||||||
|
this.server.schema.aliases.all().models,
|
||||||
|
{ pageSize: serverResultPageLimit },
|
||||||
|
);
|
||||||
|
|
||||||
|
aliasResponseHandlerSpy = sinon.spy(aliasResponseHandler);
|
||||||
|
this.server.get('aliases', aliasResponseHandlerSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it returns all data for a resource from the server', async function (assert) {
|
||||||
|
const results = await store.query('alias', {});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
aliases.length,
|
||||||
|
results.length,
|
||||||
|
'all records are returned',
|
||||||
|
);
|
||||||
|
|
||||||
|
const aliasesSortedByCreatedTime = [...aliases].sort(
|
||||||
|
(a, b) => new Date(b.created_time) - new Date(a.created_time),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
aliasesSortedByCreatedTime.map(({ name }) => name),
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
'`name` property matches between the returned results and the mirage records sorted by created_time',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports batching requests and writes to indexedDB', async function (assert) {
|
||||||
|
await store.query('alias', {});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
indexedbHandlerSpy.writeToIndexedDb.callCount,
|
||||||
|
aliases.length / testBatchLimit,
|
||||||
|
'handler #writeToIndexedDb is called once for each batch',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
applicationAdapterSpy.query.callCount,
|
||||||
|
aliases.length / testBatchLimit,
|
||||||
|
'application adapter #query is called once for each batch',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
aliasResponseHandlerSpy.callCount,
|
||||||
|
aliases.length / serverResultPageLimit,
|
||||||
|
'server handler should be called once for each page',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it loads from indexeddb only and does not fetch from api, when peekIndexedDB option is used', async function (assert) {
|
||||||
|
const callCounts = {
|
||||||
|
get writeToIndexedDb() {
|
||||||
|
return indexedbHandlerSpy.writeToIndexedDb.callCount;
|
||||||
|
},
|
||||||
|
get aliasResponseHandler() {
|
||||||
|
return aliasResponseHandlerSpy.callCount;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// before first query, no data has been fetched or written to indexeddb
|
||||||
|
// the call counts should therefore be 0
|
||||||
|
assert.strictEqual(callCounts.aliasResponseHandler, 0);
|
||||||
|
assert.strictEqual(callCounts.writeToIndexedDb, 0);
|
||||||
|
|
||||||
|
const results = await store.query('alias', {});
|
||||||
|
assert.strictEqual(results.length, 50);
|
||||||
|
|
||||||
|
// call counts expected after first query
|
||||||
|
const expectedWriteToIndexedDbCallCount = 5;
|
||||||
|
const expectedResponseHandlerCallCount = 10;
|
||||||
|
|
||||||
|
// first `store.query()` calls api and writes batches to indexededdb
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.aliasResponseHandler,
|
||||||
|
expectedResponseHandlerCallCount,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.writeToIndexedDb,
|
||||||
|
expectedWriteToIndexedDbCallCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.query('alias', {}, { peekIndexedDB: true });
|
||||||
|
assert.strictEqual(results.length, 50);
|
||||||
|
|
||||||
|
// second run does not increase call counts to api or writes to indexeddb
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.aliasResponseHandler,
|
||||||
|
expectedResponseHandlerCallCount,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.writeToIndexedDb,
|
||||||
|
expectedWriteToIndexedDbCallCount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('sorting', function () {
|
||||||
|
test('it sorts by descending `created_time` by default', async function (assert) {
|
||||||
|
const target2020 = this.server.create('target', {
|
||||||
|
name: 'Target 2020',
|
||||||
|
created_time: '2020-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2021 = this.server.create('target', {
|
||||||
|
name: 'Target 2021',
|
||||||
|
created_time: '2021-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2022 = this.server.create('target', {
|
||||||
|
name: 'Target 2022',
|
||||||
|
created_time: '2022-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2023 = this.server.create('target', {
|
||||||
|
name: 'Target 2023',
|
||||||
|
created_time: '2023-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledTargets = faker.helpers.shuffle([
|
||||||
|
target2020,
|
||||||
|
target2021,
|
||||||
|
target2022,
|
||||||
|
target2023,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(shuffledTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {});
|
||||||
|
assert.deepEqual(
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
['Target 2023', 'Target 2022', 'Target 2021', 'Target 2020'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending (default) `name` using string sorting', async function (assert) {
|
||||||
|
const target001 = this.server.create('target', { name: 'Target 001' });
|
||||||
|
const target002 = this.server.create('target', { name: 'Target 002' });
|
||||||
|
const target003 = this.server.create('target', { name: 'Target 003' });
|
||||||
|
const target004 = this.server.create('target', { name: 'Target 004' });
|
||||||
|
const target005 = this.server.create('target', { name: 'Target 005' });
|
||||||
|
|
||||||
|
const shuffledTargets = faker.helpers.shuffle([
|
||||||
|
target001,
|
||||||
|
target002,
|
||||||
|
target003,
|
||||||
|
target004,
|
||||||
|
target005,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(shuffledTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { sort: { attribute: 'name' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
['Target 001', 'Target 002', 'Target 003', 'Target 004', 'Target 005'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by descending, then ascending `name` using string sorting', async function (assert) {
|
||||||
|
const alias001 = this.server.create('alias', { name: 'Alias 001' });
|
||||||
|
const alias002 = this.server.create('alias', { name: 'Alias 002' });
|
||||||
|
const alias003 = this.server.create('alias', { name: 'Alias 003' });
|
||||||
|
const alias004 = this.server.create('alias', { name: 'Alias 004' });
|
||||||
|
|
||||||
|
const shuffledAliases = faker.helpers.shuffle([
|
||||||
|
alias001,
|
||||||
|
alias002,
|
||||||
|
alias003,
|
||||||
|
alias004,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'aliases',
|
||||||
|
createPaginatedResponseHandler(shuffledAliases, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsDesc = await store.query('alias', {
|
||||||
|
query: { sort: { attribute: 'name', direction: 'desc' } },
|
||||||
|
});
|
||||||
|
const resultsAsc = await store.query('alias', {
|
||||||
|
query: { sort: { attribute: 'name', direction: 'asc' } },
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsDesc.map(({ name }) => name),
|
||||||
|
['Alias 004', 'Alias 003', 'Alias 002', 'Alias 001'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsAsc.map(({ name }) => name),
|
||||||
|
['Alias 001', 'Alias 002', 'Alias 003', 'Alias 004'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by descending, then ascending `name` using custom string sorting', async function (assert) {
|
||||||
|
const alias1 = this.server.create('alias', { name: 'alias1' });
|
||||||
|
const alias2 = this.server.create('alias', { name: 'alias2' });
|
||||||
|
const alias3 = this.server.create('alias', { name: 'alias3' });
|
||||||
|
const alias4 = this.server.create('alias', { name: 'alias4' });
|
||||||
|
|
||||||
|
const sortName = (recordA, recordB) => {
|
||||||
|
const nameMap = {
|
||||||
|
alias3: 'Alpha',
|
||||||
|
alias4: 'Beta',
|
||||||
|
alias1: 'Delta',
|
||||||
|
alias2: 'Epsilon',
|
||||||
|
};
|
||||||
|
return String(nameMap[recordA.attributes.name]).localeCompare(
|
||||||
|
String(nameMap[recordB.attributes.name]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shuffledAliases = faker.helpers.shuffle([
|
||||||
|
alias1,
|
||||||
|
alias2,
|
||||||
|
alias3,
|
||||||
|
alias4,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'aliases',
|
||||||
|
createPaginatedResponseHandler(shuffledAliases, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsDesc = await store.query('alias', {
|
||||||
|
query: { sort: { sortFunction: sortName, direction: 'desc' } },
|
||||||
|
});
|
||||||
|
const resultsAsc = await store.query('alias', {
|
||||||
|
query: { sort: { sortFunction: sortName, direction: 'asc' } },
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsDesc.map(({ name }) => name),
|
||||||
|
['alias2', 'alias1', 'alias4', 'alias3'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsAsc.map(({ name }) => name),
|
||||||
|
['alias3', 'alias4', 'alias1', 'alias2'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('option: pushToStore', function (hooks) {
|
||||||
|
let mirageTargets;
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
mirageTargets = this.server.createList('target', 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it pushes fetched results to the store by default', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {});
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => result instanceof TargetModel),
|
||||||
|
'results are ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it pushes fethed results to the store when pushToStore is true', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {}, { pushToStore: true });
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => result instanceof TargetModel),
|
||||||
|
'results are ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does not push fetched results to the store when pushToStore is false', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {}, { pushToStore: false });
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => !(result instanceof TargetModel)),
|
||||||
|
'results are not ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it supports searching', async function (assert) {
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'Magical Target',
|
||||||
|
description: 'A target',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'Target 2',
|
||||||
|
description: 'Target with magic',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.createList('target', 4);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(this.server.schema.targets.all().models, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { search: 'magic' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
results
|
||||||
|
// search results aren't returning in a consistent order so
|
||||||
|
// the sorting is used to be able to assert
|
||||||
|
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||||
|
.map(({ name, description }) => ({ name, description })),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
description: 'A target',
|
||||||
|
name: 'Magical Target',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Target with magic',
|
||||||
|
name: 'Target 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it supports filtering', async function (assert) {
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'specific-target',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.createList('target', 5);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(this.server.schema.targets.all().models, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { filters: { name: [{ equals: 'specific-target' }] } },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(results[0].name, 'specific-target');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -381,4 +381,29 @@ module('Unit | Serializer | application', function (hooks) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// JSON values will only be trimmed if the model attr
|
||||||
|
// includes the trimWhitespace property set to true
|
||||||
|
// See preferred_endpoints attr on host-set model
|
||||||
|
test('it trims whitespace on json values', function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const record = store.createRecord('host-set', {
|
||||||
|
name: 'Host Set 1',
|
||||||
|
compositeType: 'azure',
|
||||||
|
description: 'Description',
|
||||||
|
host_catalog_id: '123',
|
||||||
|
version: 1,
|
||||||
|
preferred_endpoints: [{ value: ' option 1' }, { value: 'option 2 ' }],
|
||||||
|
sync_interval_seconds: 1,
|
||||||
|
});
|
||||||
|
assert.deepEqual(record.serialize(), {
|
||||||
|
name: 'Host Set 1',
|
||||||
|
description: 'Description',
|
||||||
|
host_catalog_id: '123',
|
||||||
|
version: 1,
|
||||||
|
preferred_endpoints: ['option 1', 'option 2'],
|
||||||
|
sync_interval_seconds: 1,
|
||||||
|
attributes: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -231,4 +231,52 @@ module('Unit | Serializer | credential library', function (hooks) {
|
|||||||
version: 1,
|
version: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it serializes vault_ssh_certificate correctly when changing type to ed25519', function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const serializer = store.serializerFor('credential-library');
|
||||||
|
store.push({
|
||||||
|
data: {
|
||||||
|
id: '1',
|
||||||
|
type: 'credential-library',
|
||||||
|
attributes: {
|
||||||
|
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||||
|
version: 1,
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
path: '/vault/path',
|
||||||
|
username: 'user',
|
||||||
|
key_type: 'rsa',
|
||||||
|
key_bits: 100,
|
||||||
|
ttl: '100',
|
||||||
|
key_id: 'id',
|
||||||
|
extensions: [{ key: 'key', value: 'value' }],
|
||||||
|
critical_options: [{ key: 'key', value: 'value' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const record = store.peekRecord('credential-library', '1');
|
||||||
|
record.key_type = 'ed25519';
|
||||||
|
|
||||||
|
const snapshot = record._createSnapshot();
|
||||||
|
const serializedRecord = serializer.serialize(snapshot);
|
||||||
|
|
||||||
|
assert.deepEqual(serializedRecord, {
|
||||||
|
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||||
|
credential_store_id: null,
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
attributes: {
|
||||||
|
path: '/vault/path',
|
||||||
|
username: 'user',
|
||||||
|
key_type: 'ed25519',
|
||||||
|
key_bits: null,
|
||||||
|
ttl: '100',
|
||||||
|
key_id: 'id',
|
||||||
|
extensions: { key: 'value' },
|
||||||
|
critical_options: { key: 'value' },
|
||||||
|
},
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -54,7 +54,7 @@ module('Unit | Serializer | host set', function (hooks) {
|
|||||||
description: 'Description',
|
description: 'Description',
|
||||||
host_catalog_id: '123',
|
host_catalog_id: '123',
|
||||||
version: 1,
|
version: 1,
|
||||||
preferred_endpoints: [{ value: 'option 1' }, { value: 'option 2' }],
|
preferred_endpoints: [{ value: ' option 1' }, { value: 'option 2 ' }],
|
||||||
sync_interval_seconds: 1,
|
sync_interval_seconds: 1,
|
||||||
filters: [{ value: 'filter 1' }, { value: 'filter 2' }],
|
filters: [{ value: 'filter 1' }, { value: 'filter 2' }],
|
||||||
});
|
});
|
||||||
@@ -208,4 +208,22 @@ module('Unit | Serializer | host set', function (hooks) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it handles errors correctly', function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const serializer = store.serializerFor('host-set');
|
||||||
|
const schema = store.modelFor('host-set');
|
||||||
|
|
||||||
|
const errors = {
|
||||||
|
base: ['Error in provided request.'],
|
||||||
|
filter: ['This field is required.'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializer.extractErrors(store, schema, errors, null);
|
||||||
|
|
||||||
|
assert.deepEqual(result, {
|
||||||
|
base: ['Error in provided request.'],
|
||||||
|
filter_string: ['This field is required.'],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -183,7 +183,8 @@ module('Unit | Serializer | Managed group', function (hooks) {
|
|||||||
test('it normalizes correctly', function (assert) {
|
test('it normalizes correctly', function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
const serializer = store.serializerFor('managed-group');
|
const serializer = store.serializerFor('managed-group');
|
||||||
const hostSetModelClass = store.createRecord('managed-group').constructor;
|
const managedGroupModelClass =
|
||||||
|
store.createRecord('managed-group').constructor;
|
||||||
const payload = {
|
const payload = {
|
||||||
id: '1',
|
id: '1',
|
||||||
description: 'Description for the test',
|
description: 'Description for the test',
|
||||||
@@ -196,7 +197,7 @@ module('Unit | Serializer | Managed group', function (hooks) {
|
|||||||
};
|
};
|
||||||
const normalized = serializer.normalizeSingleResponse(
|
const normalized = serializer.normalizeSingleResponse(
|
||||||
store,
|
store,
|
||||||
hostSetModelClass,
|
managedGroupModelClass,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -219,4 +220,22 @@ module('Unit | Serializer | Managed group', function (hooks) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it handles errors correctly', function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const serializer = store.serializerFor('managed-group');
|
||||||
|
const schema = store.modelFor('managed-group');
|
||||||
|
|
||||||
|
const errors = {
|
||||||
|
base: ['Error in provided request.'],
|
||||||
|
filter: ['This field is required.'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = serializer.extractErrors(store, schema, errors, null);
|
||||||
|
|
||||||
|
assert.deepEqual(result, {
|
||||||
|
base: ['Error in provided request.'],
|
||||||
|
filter_string: ['This field is required.'],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -26,7 +26,7 @@ module('Unit | Serializer | storage bucket', function (hooks) {
|
|||||||
bucket_name: 'bucketname',
|
bucket_name: 'bucketname',
|
||||||
bucket_prefix: 'bucketprefix',
|
bucket_prefix: 'bucketprefix',
|
||||||
worker_filter: 'workerfilter',
|
worker_filter: 'workerfilter',
|
||||||
region: 'eu-west-1',
|
region: 'eu-west-1 ',
|
||||||
access_key_id: 'foobars',
|
access_key_id: 'foobars',
|
||||||
secret_access_key: 'testing',
|
secret_access_key: 'testing',
|
||||||
disable_credential_rotation: true,
|
disable_credential_rotation: true,
|
||||||
@@ -117,7 +117,7 @@ module('Unit | Serializer | storage bucket', function (hooks) {
|
|||||||
access_key_id: '',
|
access_key_id: '',
|
||||||
secret_access_key: '',
|
secret_access_key: '',
|
||||||
disable_credential_rotation: true,
|
disable_credential_rotation: true,
|
||||||
role_arn: 'arn',
|
role_arn: 'arn ',
|
||||||
role_external_id: 'Example987',
|
role_external_id: 'Example987',
|
||||||
role_session_name: 'my-session',
|
role_session_name: 'my-session',
|
||||||
role_tags: [
|
role_tags: [
|
||||||
@@ -160,12 +160,12 @@ module('Unit | Serializer | storage bucket', function (hooks) {
|
|||||||
bucket_prefix: 'bucketprefix',
|
bucket_prefix: 'bucketprefix',
|
||||||
worker_filter: 'workerfilter',
|
worker_filter: 'workerfilter',
|
||||||
plugin: { name: 'aws' },
|
plugin: { name: 'aws' },
|
||||||
region: 'eu-west-1',
|
region: ' eu-west-1 ',
|
||||||
access_key_id: 'foobars',
|
access_key_id: 'foobars',
|
||||||
secret_access_key: 'testing',
|
secret_access_key: 'testing',
|
||||||
disable_credential_rotation: true,
|
disable_credential_rotation: true,
|
||||||
version: 1,
|
version: 1,
|
||||||
role_arn: 'role_arn_test',
|
role_arn: ' role_arn_test ',
|
||||||
role_external_id: 'role_external_id_test',
|
role_external_id: 'role_external_id_test',
|
||||||
role_session_name: 'role_session_test',
|
role_session_name: 'role_session_test',
|
||||||
role_tags: [
|
role_tags: [
|
||||||
|
|||||||
@@ -0,0 +1,488 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert, module, test } from 'qunit';
|
||||||
|
import { sortResults } from 'api/utils/sort-results';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
|
||||||
|
function createJsonApiMockRecord(id, type, attributes) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module('Unit | Utility | sortResults', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
module('Defaults', function () {
|
||||||
|
test("throws an error when an attribute is not id, created_time or defined as a schema attribute, even when it exists on the record's attribute object", function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'Result 01',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
name: 'Result 02',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
name: 'Result 03',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const querySort = { attribute: 'name' };
|
||||||
|
|
||||||
|
// Note: `name` is not defined as a schema attribute but exists on the record
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('date', { type: 'date' });
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => sortResults(shuffledResults, { querySort, schema }),
|
||||||
|
/Error: The attribute "name" does not map to the model definition of type "target". Supported sortable attributes are 'id', 'created_time', or 'date'/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws an error when results do not contain a valid sort direction', function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'Result 01',
|
||||||
|
});
|
||||||
|
|
||||||
|
const querySort = {
|
||||||
|
attribute: 'name',
|
||||||
|
direction: 'invalid sort direction',
|
||||||
|
};
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('name', { type: 'string' });
|
||||||
|
assert.throws(
|
||||||
|
() => sortResults([result01], { querySort, schema }),
|
||||||
|
/Error: Invalid sort direction/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when passing no querySort, sorts using default created_time, descending order and string sorting', async function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
created_time: '2021',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
created_time: '2022',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
created_time: '2023',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
created_time: '2024',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = {};
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('created_time', { type: 'string' });
|
||||||
|
const sortedResults = sortResults(shuffledResults, {
|
||||||
|
querySort,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { created_time } }) => created_time),
|
||||||
|
['2024', '2023', '2022', '2021'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when sorting by id, without being on the results attributes it sorts as expected ', function () {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'Result 01',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
name: 'Result 02',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
name: 'Result 03',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
name: 'Result 04',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = { attribute: 'id' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('id', { type: 'string' });
|
||||||
|
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ id }) => id),
|
||||||
|
['id_01', 'id_02', 'id_03', 'id_04'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('string sorting', function () {
|
||||||
|
test('it sorts by ascending (default) `name` using string sorting', async function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'Result 01',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
name: 'Result 02',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
name: 'Result 03',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
name: 'Result 04',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = { attribute: 'name' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('name', { type: 'string' });
|
||||||
|
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { name } }) => name),
|
||||||
|
['Result 01', 'Result 02', 'Result 03', 'Result 04'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending then descending `name` using string sorting', async function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'Result 01',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
name: 'Result 02',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
name: 'Result 03',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
name: 'Result 04',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySortAsc = { attribute: 'name', direction: 'asc' };
|
||||||
|
const querySortDesc = { attribute: 'name', direction: 'desc' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('name', { type: 'string' });
|
||||||
|
const sortedResultsAsc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortAsc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
const sortedResultsDesc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortDesc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsAsc.map(({ attributes: { name } }) => name),
|
||||||
|
['Result 01', 'Result 02', 'Result 03', 'Result 04'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsDesc.map(({ attributes: { name } }) => name),
|
||||||
|
['Result 04', 'Result 03', 'Result 02', 'Result 01'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('date sorting', function () {
|
||||||
|
test('it sorts by ascending (default) `date` using date sorting', function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
date: '2021',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
date: '2022',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
date: '2023',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
date: '2024',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = { attribute: 'date' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('date', { type: 'date' });
|
||||||
|
|
||||||
|
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { date } }) => date),
|
||||||
|
['2021', '2022', '2023', '2024'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending then descending `date` using date sorting', function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
date: '2021',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
date: '2022',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
date: '2023',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
date: '2024',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySortAsc = { attribute: 'date', direction: 'asc' };
|
||||||
|
const querySortDesc = { attribute: 'date', direction: 'desc' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('date', { type: 'date' });
|
||||||
|
|
||||||
|
const sortedResultsAsc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortAsc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
const sortedResultsDesc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortDesc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsAsc.map(({ attributes: { date } }) => date),
|
||||||
|
['2021', '2022', '2023', '2024'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsDesc.map(({ attributes: { date } }) => date),
|
||||||
|
['2024', '2023', '2022', '2021'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('number sorting', function () {
|
||||||
|
test('it sorts by ascending (default) `sessions`', function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 10,
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 200,
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 3333,
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 40444,
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = { attribute: 'sessions' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('sessions', { type: 'number' });
|
||||||
|
|
||||||
|
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { sessions } }) => sessions),
|
||||||
|
[10, 200, 3333, 40444],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending then descending `sessions`', function (assert) {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 10,
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 200,
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 3333,
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
sessions: 40444,
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySortAsc = { attribute: 'sessions', direction: 'asc' };
|
||||||
|
const querySortDesc = { attribute: 'sessions', direction: 'desc' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('sessions', { type: 'number' });
|
||||||
|
|
||||||
|
const sortedResultsAsc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortAsc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
const sortedResultsDesc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortDesc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsAsc.map(({ attributes: { sessions } }) => sessions),
|
||||||
|
[10, 200, 3333, 40444],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsDesc.map(({ attributes: { sessions } }) => sessions),
|
||||||
|
[40444, 3333, 200, 10],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('boolean sorting', function () {
|
||||||
|
test('it sorts by ascending (default) `active`', function (assert) {
|
||||||
|
const result01 = { attributes: { active: true } };
|
||||||
|
const result02 = { attributes: { active: false } };
|
||||||
|
const result03 = { attributes: { active: false } };
|
||||||
|
const result04 = { attributes: { active: true } };
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySort = { attribute: 'active' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('active', { type: 'boolean' });
|
||||||
|
|
||||||
|
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { active } }) => active),
|
||||||
|
[false, false, true, true],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending then descending `active`', function (assert) {
|
||||||
|
const result01 = { attributes: { active: true } };
|
||||||
|
const result02 = { attributes: { active: false } };
|
||||||
|
const result03 = { attributes: { active: false } };
|
||||||
|
const result04 = { attributes: { active: true } };
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
const querySortAsc = { attribute: 'active', direction: 'asc' };
|
||||||
|
const querySortDesc = { attribute: 'active', direction: 'desc' };
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
schema.attributes.set('active', { type: 'boolean' });
|
||||||
|
|
||||||
|
const sortedResultsAsc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortAsc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
const sortedResultsDesc = sortResults(shuffledResults, {
|
||||||
|
querySort: querySortDesc,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsAsc.map(({ attributes: { active } }) => active),
|
||||||
|
[false, false, true, true],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResultsDesc.map(({ attributes: { active } }) => active),
|
||||||
|
[true, true, false, false],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('custom function sorting', function () {
|
||||||
|
const result01 = createJsonApiMockRecord('id_01', 'target', {
|
||||||
|
name: 'target1',
|
||||||
|
});
|
||||||
|
const result02 = createJsonApiMockRecord('id_02', 'target', {
|
||||||
|
name: 'target2',
|
||||||
|
});
|
||||||
|
const result03 = createJsonApiMockRecord('id_03', 'target', {
|
||||||
|
name: 'target3',
|
||||||
|
});
|
||||||
|
const result04 = createJsonApiMockRecord('id_04', 'target', {
|
||||||
|
name: 'target4',
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortFunction = (recordA, recordB) => {
|
||||||
|
const nameMap = {
|
||||||
|
target3: 'Alpha',
|
||||||
|
target4: 'Beta',
|
||||||
|
target1: 'Delta',
|
||||||
|
target2: 'Epsilon',
|
||||||
|
};
|
||||||
|
return String(nameMap[recordA.attributes.name]).localeCompare(
|
||||||
|
String(nameMap[recordB.attributes.name]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const schema = { attributes: new Map() };
|
||||||
|
|
||||||
|
test.each(
|
||||||
|
'it sorts by `name` using custom sorting function',
|
||||||
|
{
|
||||||
|
'ascending (default)': {
|
||||||
|
querySort: { sortFunction },
|
||||||
|
expectedResults: ['target3', 'target4', 'target1', 'target2'],
|
||||||
|
},
|
||||||
|
descending: {
|
||||||
|
querySort: { sortFunction, direction: 'desc' },
|
||||||
|
expectedResults: ['target2', 'target1', 'target4', 'target3'],
|
||||||
|
},
|
||||||
|
ascending: {
|
||||||
|
querySort: { sortFunction, direction: 'asc' },
|
||||||
|
expectedResults: ['target3', 'target4', 'target1', 'target2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
function (assert, input) {
|
||||||
|
const shuffledResults = faker.helpers.shuffle([
|
||||||
|
result01,
|
||||||
|
result02,
|
||||||
|
result03,
|
||||||
|
result04,
|
||||||
|
]);
|
||||||
|
schema.attributes.set('name', { type: 'string' });
|
||||||
|
const sortedResults = sortResults(shuffledResults, {
|
||||||
|
querySort: input.querySort,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sortedResults.map(({ attributes: { name } }) => name),
|
||||||
|
input.expectedResults,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,8 +12,8 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{diff,md}]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.hbs]
|
[*.hbs]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.{diff,md}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
/**
|
|
||||||
Ember CLI sends analytics information by default. The data is completely
|
|
||||||
anonymous, but there are times when you might want to disable this behavior.
|
|
||||||
|
|
||||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
|
||||||
*/
|
|
||||||
"disableAnalytics": false,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
||||||
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
||||||
|
|||||||
Loaded 100 of 1016 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user