diff --git a/addons/api/addon/services/resource-filter-store.js b/addons/api/addon/services/resource-filter-store.js index 8bd4130ae..9dbca0f5e 100644 --- a/addons/api/addon/services/resource-filter-store.js +++ b/addons/api/addon/services/resource-filter-store.js @@ -52,7 +52,7 @@ export class ResourceFilter { * @return {string} */ equals(key, value) { - return `"/item/${key}" == "${value}"`; + return value !== null ? `"/item/${key}" == "${value}"` : null; } /** @@ -76,7 +76,7 @@ export class ResourceFilter { * @return {string} */ parenthetical(expression) { - return `(${expression})`; + return expression ? `(${expression})` : null; } } diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 0fe8fb6b1..da0632da8 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -55,8 +55,9 @@ export default function () { this.get( '/auth-methods', - ({ authMethods }, { queryParams: { scope_id: scopeId } }) => { - return authMethods.where({ scopeId }); + ({ authMethods }, { queryParams: { scope_id: scopeId, filter } }) => { + const resultSet = authMethods.where({ scopeId }); + return resultSet.filter(makeBooleanFilter(filter)); } ); this.post('/auth-methods'); diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js index 965234b5d..9291f8a3e 100644 --- a/addons/api/mirage/scenarios/default.js +++ b/addons/api/mirage/scenarios/default.js @@ -22,7 +22,7 @@ export default function (server) { // Auth const globalAuthMethods = server.createList( 'auth-method', - 4, + 1, { scope: globalScope }, 'withAccountsAndUsersAndManagedGroups' ); diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index 61978ee06..b8395a20f 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -46,6 +46,9 @@ export function resourceFilterParam(allowedValues, defaultValue) { // If the route has no query params specified yet, declare them. if (!Object.keys(target.queryParams).length) target.queryParams = {}; + // If the route has no resource filters list specified yet, create one. + if (!target.resourceFilterParams) target.resourceFilterParams = []; + // Add the resource filter query parameter to the route. Resource filter // param changes refresh the model but do not contribute to browser history. // See https://guides.emberjs.com/release/routing/query-params/ @@ -54,6 +57,9 @@ export function resourceFilterParam(allowedValues, defaultValue) { replace: true, }; + // Note the name of this filter param. + target.resourceFilterParams.push(name); + // Store the allowed values for future lookup // (via `ResourceFilterParamHelper`). target[filterAllowedValuesKey] = allowedValues; diff --git a/addons/core/addon/helpers/has-resource-filter-param-selections.js b/addons/core/addon/helpers/has-resource-filter-param-selections.js new file mode 100644 index 000000000..6f6015078 --- /dev/null +++ b/addons/core/addon/helpers/has-resource-filter-param-selections.js @@ -0,0 +1,73 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; +import { getOwner } from '@ember/application'; +import { action } from '@ember/object'; + +/** + * Use this helper to determine if a resource filter for a route has any + * currently selected value. + * + * @see resourceFilterParam (decorator) + * + * @example + * + * {{#if (has-resource-filter-param-selections 'route.name' 'field')}} + * Field has filter selections. + * {{/if}} + */ +export default class HasResourceFilterParamSelectionsHelper extends Helper { + // =services + + @service router; + + // =methods + + // =lifecycle management methods + + /** + * Recompute this helper when the route changes. + */ + constructor() { + super(...arguments); + this.router.on('routeDidChange', this.routeDidChange); + } + + /** + * Stop listening to route events when this helper is destroyed. + */ + willDestroy() { + this.router.off('routeDidChange', this.routeDidChange); + super.willDestroy(); + } + + // =compute method + + /** + * Returns true if the specified route has a value set for + * any resource filter. + * @param {string} routeName + * @return {boolean} + */ + compute([routeName]) { + const owner = getOwner(this); + const route = owner.lookup(`route:${routeName}`); + const names = route.resourceFilterParams; + const selectedValues = names.map((name) => route[name] || null).flat(); + const anyTruthy = selectedValues.reduce( + (previousValue, currentValue) => previousValue || currentValue, + false + ); + return Boolean(anyTruthy); + } + + // =actions + + /** + * Triggers a recompute (see above) when the route changes, especially when + * route query params change. + */ + @action + routeDidChange() { + this.recompute(); + } +} diff --git a/addons/core/app/helpers/has-resource-filter-param-selections.js b/addons/core/app/helpers/has-resource-filter-param-selections.js new file mode 100644 index 000000000..b6ab1bc86 --- /dev/null +++ b/addons/core/app/helpers/has-resource-filter-param-selections.js @@ -0,0 +1 @@ +export { default } from 'core/helpers/has-resource-filter-param-selections'; diff --git a/addons/core/package.json b/addons/core/package.json index b51acc24b..fd10d6d9f 100644 --- a/addons/core/package.json +++ b/addons/core/package.json @@ -37,10 +37,10 @@ "ember-intl": "^5.7.0", "ember-loading": "^0.4.0", "ember-route-action-helper": "^2.0.8", + "ember-truth-helpers": "^3.0.0", "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", @@ -73,6 +73,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-qunit": "^6.2.0", "faker": "^5.5.3", + "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1", "loader.js": "^4.7.0", "npm-run-all": "^4.1.5", "prettier": "^2.4.0", diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index 144ced636..118c5f601 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -35,3 +35,4 @@ close: Close toggle-secret: Toggle secret visibility minimize: Minimize fullscreen: Fullscreen +clear-all-filters: Clear all filters diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index 8f98f633c..73a720d62 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -29,11 +29,13 @@ titles: welcome-to-boundary: Welcome to Boundary label: Label value: Value + no-results: No Results descriptions: empty-set: There are no items to display yet. You may be able to add items or try back later. origin-initialization: To get started, please enter your origin URL create-but-not-list: You do not have permission to list {resource}, but you can create new instances. neither-list-nor-create: You do not have permission to list or create {resource}. + no-results: We found no matching records. questions: delete-confirm: Are you sure you want to delete this resource? remove-confirm: Are you sure you want to remove this association? diff --git a/addons/rose/addon/components/rose/dropdown/index.hbs b/addons/rose/addon/components/rose/dropdown/index.hbs index 5d02e87e8..d3679b3a6 100644 --- a/addons/rose/addon/components/rose/dropdown/index.hbs +++ b/addons/rose/addon/components/rose/dropdown/index.hbs @@ -18,6 +18,9 @@ {{@text}} + {{#if @count}} + {{@count}} + {{/if}} diff --git a/addons/rose/addon/components/rose/dropdown/index.stories.mdx b/addons/rose/addon/components/rose/dropdown/index.stories.mdx index 1ee5d754a..625447e19 100644 --- a/addons/rose/addon/components/rose/dropdown/index.stories.mdx +++ b/addons/rose/addon/components/rose/dropdown/index.stories.mdx @@ -1,6 +1,6 @@ import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; import { hbs } from 'ember-cli-htmlbars'; -import { boolean, object } from '@storybook/addon-knobs'; +import { number, boolean, object } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; @@ -12,7 +12,7 @@ A simple dropdown component with support for links and buttons. {{ template: hbs` - + Menu item Menu item @@ -24,6 +24,7 @@ A simple dropdown component with support for links and buttons. `, context: { + count: number('count', 0), showCaret: boolean('showCaret', true), }, }} diff --git a/addons/rose/addon/styles/rose/components/dropdown/_dropdown.scss b/addons/rose/addon/styles/rose/components/dropdown/_dropdown.scss index b8f3450f4..62a8f3d36 100644 --- a/addons/rose/addon/styles/rose/components/dropdown/_dropdown.scss +++ b/addons/rose/addon/styles/rose/components/dropdown/_dropdown.scss @@ -39,6 +39,11 @@ $chevronGrey: url("data:image/svg+xml;utf-8,`); + assert.equal(find('.rose-badge').textContent.trim(), '2'); + }); + test('it supports an icon', async function (assert) { await render( hbs`` diff --git a/ui/admin/app/routes/scopes/scope/auth-methods.js b/ui/admin/app/routes/scopes/scope/auth-methods.js index 687c5ef81..e83a95911 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods.js @@ -4,6 +4,7 @@ import { action } from '@ember/object'; import loading from 'ember-loading/decorator'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; +import { resourceFilterParam } from 'core/decorators/resource-filter-param'; export default class ScopesScopeAuthMethodsRoute extends Route { // =services @@ -12,6 +13,11 @@ export default class ScopesScopeAuthMethodsRoute extends Route { @service notify; @service session; @service can; + @service resourceFilterStore; + + // =attributes + + @resourceFilterParam(['password', 'oidc']) type; // =methods @@ -32,7 +38,12 @@ export default class ScopesScopeAuthMethodsRoute extends Route { if ( this.can.can('list collection', scope, { collection: 'auth-methods' }) ) { - return this.store.query('auth-method', { scope_id }); + const { type } = this; + return this.resourceFilterStore.queryBy( + 'auth-method', + { type }, + { scope_id } + ); } } @@ -185,4 +196,22 @@ export default class ScopesScopeAuthMethodsRoute extends Route { const value = { from, to }; array.addObject(value); } + + /** + * Sets the specified resource filter field to the specified value. + * @param {string} field + * @param value + */ + @action + filterBy(field, value) { + this[field] = value; + } + + /** + * Clears and filter selections. + */ + @action + clearAllFilters() { + this.type = []; + } } diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs index f4e285c77..d6075980e 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs @@ -35,8 +35,58 @@ - {{#if @model}} + {{#if + (or + @model + (has-resource-filter-param-selections 'scopes.scope.auth-methods') + ) + }} + + + {{#with + (resource-filter-param 'scopes.scope.auth-methods' 'type') + as |filter| + }} + + + + + + + + {{/with}} + + {{#if + (has-resource-filter-param-selections 'scopes.scope.auth-methods') + }} + {{t 'actions.clear-all-filters'}} + {{/if}} + + + {{/if}} + + {{#if @model}} {{#if (feature-flag 'primary-auth-method')}} {{#unless this.hasPrimaryAuthMethod}} + {{else if (has-resource-filter-param-selections + 'scopes.scope.auth-methods' + )}} + + + + + {{t 'descriptions.no-results'}} + + + + {{else}} diff --git a/yarn.lock b/yarn.lock index 3d3cb02ab..8bcb1a8fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6993,14 +6993,14 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@4.14.2, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.16.7, browserslist@^4.17.3: - version "4.17.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.4.tgz#72e2508af2a403aec0a49847ef31bd823c57ead4" - integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ== + version "4.17.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" + integrity sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw== dependencies: - caniuse-lite "^1.0.30001265" - electron-to-chromium "^1.3.867" + caniuse-lite "^1.0.30001274" + electron-to-chromium "^1.3.886" escalade "^3.1.1" - node-releases "^2.0.0" + node-releases "^2.0.1" picocolors "^1.0.0" bser@2.1.1: @@ -7311,10 +7311,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001265: - version "1.0.30001271" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz#0dda0c9bcae2cf5407cd34cac304186616cc83e8" - integrity sha512-BBruZFWmt3HFdVPS8kceTBIguKxu4f99n5JNp06OlPD/luoAMIaIK5ieV5YjnBLH3Nysai9sxj9rpJj4ZisXOA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001274: + version "1.0.30001279" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz#eb06818da481ef5096a3b3760f43e5382ed6b0ce" + integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ== capture-exit@^2.0.0: version "2.0.0" @@ -9151,10 +9151,10 @@ electron-rebuild@^2.0.3: tar "^6.0.5" yargs "^16.0.0" -electron-to-chromium@^1.3.867: - version "1.3.884" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.884.tgz#0cd8c3a80271fd84a81f284c60fb3c9ecb33c166" - integrity sha512-kOaCAa+biA98PwH5BpCkeUeTL6mCeg8p3Q3OhqzPyqhu/5QUnWAN2wr/3IK8xMQxIV76kfoQpP+Bn/wij/jXrg== +electron-to-chromium@^1.3.886: + version "1.3.895" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.895.tgz#9b0f8f2e32d8283bbb200156fd5d8dfd775f31ed" + integrity sha512-9Ww3fB8CWctjqHwkOt7DQbMZMpal2x2reod+/lU4b9axO1XJEDUpPMBxs7YnjLhhqpKXIIB5SRYN/B4K0QpvyQ== element-resize-detector@^1.2.2: version "1.2.3" @@ -10335,6 +10335,13 @@ ember-template-recast@^5.0.1, ember-template-recast@^5.0.3: tmp "^0.2.1" workerpool "^6.1.4" +ember-truth-helpers@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ember-truth-helpers/-/ember-truth-helpers-3.0.0.tgz#86766bdca4ac9b86bce3d262dff2aabc4a0ea384" + integrity sha512-hPKG9QqruAELh0li5xaiLZtr88ioWYxWCXisAWHWE0qCP4a2hgtuMzKUPpiTCkltvKjuqpzTZCU4VhQ+IlRmew== + dependencies: + ember-cli-babel "^7.22.1" + ember-try-config@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ember-try-config/-/ember-try-config-3.0.0.tgz#012d8c90cae9eb624e2b62040bf7e76a1aa58edc" @@ -16669,7 +16676,7 @@ node-pre-gyp@^0.11.0: semver "^5.3.0" tar "^4" -node-releases@^2.0.0: +node-releases@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==