diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js
index 68c7f6b94..b1481adf9 100644
--- a/addons/api/mirage/config.js
+++ b/addons/api/mirage/config.js
@@ -4,6 +4,7 @@ import { authHandler, deauthHandler } from './route-handlers/auth';
import { pickRandomStatusString } from './factories/session';
import { Response } from 'miragejs';
import initializeMockIPC from './scenarios/ipc';
+import makeBooleanFilter from './helpers/bexpr-filter';
export default function () {
initializeMockIPC(this);
@@ -408,7 +409,8 @@ export default function () {
this.get(
'/sessions',
- function ({ sessions }, { queryParams: { scope_id, recursive } }) {
+ function ({ sessions }, { queryParams: { scope_id, recursive, filter } }) {
+ let resultSet;
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// To populate sessions for logged in user,
@@ -427,17 +429,19 @@ export default function () {
});
}
if (recursive && scope_id === 'global') {
- return sessions.all();
+ resultSet = sessions.all();
} else if (recursive) {
- return sessions.where((session) => {
+ resultSet = sessions.where((session) => {
const sessionModel = sessions.find(session.id);
return (
session.scopeId === scope_id ||
sessionModel?.scope?.scope?.id === scope_id
);
});
+ } else {
+ resultSet = sessions.where((session) => session.scopeId === scope_id);
}
- return sessions.where((session) => session.scopeId === scope_id);
+ return resultSet.filter(makeBooleanFilter(filter));
}
);
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
diff --git a/addons/api/mirage/helpers/bexpr-filter.js b/addons/api/mirage/helpers/bexpr-filter.js
new file mode 100644
index 000000000..e2cffef02
--- /dev/null
+++ b/addons/api/mirage/helpers/bexpr-filter.js
@@ -0,0 +1,35 @@
+import { makeEvaluator } from 'js-bexpr';
+import { underscore } from '@ember/string';
+
+/**
+ * Because Mirage, a record cannot be serialized outside of a route handler
+ * context. Here is a quick and dirty attempt to serialize attribute names
+ * to underscore format. This is not entirely realistic, but may suffice.
+ */
+const quickSerialize = (item) => {
+ const json = item.toJSON();
+ Object.keys(json).forEach((key) => {
+ const newKey = underscore(key);
+ json[newKey] = json[key];
+ });
+ return json;
+};
+
+/**
+ * Creates a filter function that operates on a single object, which itself
+ * returns the result of evaluating the expression against the object.
+ */
+export default (expression) => {
+ if (expression) {
+ const evaluator = makeEvaluator(expression);
+ // Since API query selectors begin with `/item`, the instance is wrapped.
+ return (item) => {
+ const json = quickSerialize(item);
+ return evaluator({ item: json });
+ };
+ } else {
+ // If no expression was passed, always return true,
+ // thus filtering out none.
+ return () => true;
+ }
+};
diff --git a/addons/api/package.json b/addons/api/package.json
index 8be07dd77..1012dd943 100644
--- a/addons/api/package.json
+++ b/addons/api/package.json
@@ -33,6 +33,7 @@
"ember-fetch": "^8.1.1"
},
"devDependencies": {
+ "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
"@babel/core": "^7.15.8",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
diff --git a/addons/core/package.json b/addons/core/package.json
index faf6a1be3..199973f38 100644
--- a/addons/core/package.json
+++ b/addons/core/package.json
@@ -41,6 +41,7 @@
"rose": "*"
},
"devDependencies": {
+ "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@glimmer/component": "^1.0.4",
diff --git a/addons/rose/addon/components/rose/form/input/index.hbs b/addons/rose/addon/components/rose/form/input/index.hbs
index 9bf8003e3..889ca0639 100644
--- a/addons/rose/addon/components/rose/form/input/index.hbs
+++ b/addons/rose/addon/components/rose/form/input/index.hbs
@@ -34,16 +34,22 @@
{{/if}}
-
-
+
+ {{#if @icon}}
+
+ {{/if}}
+
+
{{yield (hash
errors=(component 'rose/form/errors'
id=(concat 'errors-' this.id)
diff --git a/addons/rose/addon/components/rose/form/input/index.stories.mdx b/addons/rose/addon/components/rose/form/input/index.stories.mdx
index 041507976..a0b6080f2 100644
--- a/addons/rose/addon/components/rose/form/input/index.stories.mdx
+++ b/addons/rose/addon/components/rose/form/input/index.stories.mdx
@@ -18,6 +18,7 @@ Renders a simple form input.
@helperText="Enter your email"
@error={{false}}
@disabled={{false}}
+ @icon='flight-icons/svg/search-16'
readonly={{false}}
/>
```
@@ -52,6 +53,7 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
@error={{false}}
@disabled={{disabled}}
readonly={{read-only}}
+ @icon={{icon}}
/>
`,
context: {
@@ -70,6 +72,14 @@ Or a fully contextual component without a wrapper, for maximum flexibility.
helperText: text('helperText', 'Helper text'),
disabled: boolean('disabled', false),
'read-only': boolean('readonly', false),
+ icon: select(
+ 'icon',
+ {
+ 'none': null,
+ 'flight-icons/svg/search-16': 'flight-icons/svg/search-16',
+ },
+ 'flight-icons/svg/search-16',
+ ),
}
}}
diff --git a/addons/rose/addon/styles/rose/components/form/_input.scss b/addons/rose/addon/styles/rose/components/form/_input.scss
index bdfd5fff9..214f57734 100644
--- a/addons/rose/addon/styles/rose/components/form/_input.scss
+++ b/addons/rose/addon/styles/rose/components/form/_input.scss
@@ -5,6 +5,18 @@
margin-bottom: sizing.rems(m);
}
+.rose-input-wrapper {
+ position: relative;
+ .rose-icon {
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ }
+ .rose-icon + .rose-form-input-field {
+ padding-left: 30px;
+ }
+}
+
.rose-form-input-field {
@include type.type(m);
display: inline-block;
diff --git a/addons/rose/tests/integration/components/rose/form/input-test.js b/addons/rose/tests/integration/components/rose/form/input-test.js
index 2f6c6cc56..d053698ab 100644
--- a/addons/rose/tests/integration/components/rose/form/input-test.js
+++ b/addons/rose/tests/integration/components/rose/form/input-test.js
@@ -58,6 +58,11 @@ module('Integration | Component | rose/form/input', function (hooks) {
assert.ok(await find('[disabled]'));
});
+ test('it supports an icon using @icon', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-icon'));
+ });
+
test('it supports readonly attribute', async function (assert) {
await render(hbs``);
assert.ok(await find('[readonly]'));
diff --git a/ui/admin/package.json b/ui/admin/package.json
index 43af20716..215991910 100644
--- a/ui/admin/package.json
+++ b/ui/admin/package.json
@@ -35,6 +35,7 @@
"ember-named-blocks-polyfill": "^0.2.4"
},
"devDependencies": {
+ "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
"@babel/core": "^7.15.5",
"@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^1.0.2",
diff --git a/ui/desktop/package.json b/ui/desktop/package.json
index 6c6c7a3d3..590e92035 100755
--- a/ui/desktop/package.json
+++ b/ui/desktop/package.json
@@ -53,6 +53,7 @@
"ember-named-blocks-polyfill": "^0.2.4"
},
"devDependencies": {
+ "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
"@doyensec/electronegativity": "^1.9.1",
"@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^1.0.2",
diff --git a/yarn.lock b/yarn.lock
index 42ce135bb..d9cd99d55 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12051,6 +12051,11 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
+foreach@^2.0.4:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+ integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
+
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -14674,6 +14679,12 @@ jquery@^3.5.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
+js-bexpr@hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1:
+ version "0.0.0"
+ resolved "https://codeload.github.com/hashicorp/js-bexpr/tar.gz/9b4a4b54d85eba67fdfc0990133d1518d890b1e1"
+ dependencies:
+ json-pointer "^0.6.1"
+
js-string-escape@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
@@ -14843,6 +14854,13 @@ json-parse-even-better-errors@^2.3.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+json-pointer@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d"
+ integrity sha512-3OvjqKdCBvH41DLpV4iSt6v2XhZXV1bPB4OROuknvUXI7ZQNofieCPkmE26stEJ9zdQuvIxDHCuYhfgxFAAs+Q==
+ dependencies:
+ foreach "^2.0.4"
+
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"