feat: 🎸 tweak resource-filter service and apply to sessions

This commit is contained in:
Randall Morey committed 2021-11-09 12:42:39 -05:00
1 parent cd6f87b796
commit 751a72a2d8
7 files changed
+108 -85

No files matched your search

@@ -0,0 +1,87 @@
import Service from '@ember/service';
import { inject as service } from '@ember/service';
/**
* Takes a POJO representing a query and converts it to a boolean expression
* string which is understood by the API.
*
* @example
* const filter = new ResourceFilter({
* type: 'oidc',
* status: ['active', 'pending']
* });
* console.log(filter.queryExpression);
* // ("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending")
*/
export class ResourceFilter {
// =attributes
#filterObject;
/**
* @type {string}
*/
get queryExpression() {
const clauses = Object.keys(this.#filterObject)
.map((key) => {
const value = this.#filterObject[key];
const valuesArray = Array.isArray(value) ? value : [value];
return this.parenthetical(
this.or(valuesArray.map((value) => this.equals(key, value)))
);
})
.flat();
return this.and(clauses);
}
// =methods
/**
* @param {object} filterObject
*/
constructor(filterObject = {}) {
this.#filterObject = Object.assign({}, filterObject);
}
/**
* @param {string} key
* @param value
*/
equals(key, value) {
return `"/item/${key}" == "${value}"`;
}
/**
* @param {string[]} clauses
*/
and(clauses) {
return clauses.join(' and ');
}
/**
* @param {string[]} clauses
*/
or(clauses) {
return clauses.join(' or ');
}
/**
* @param {string} expression
*/
parenthetical(expression) {
return `(${expression})`;
}
}
export default class ResourceFilterStoreService extends Service {
@service store;
queryBy(modelName, filterObject = {}, storeQuery = {}) {
console.log(filterObject);
const filter = new ResourceFilter(filterObject);
const query = Object.assign({}, storeQuery);
// Apply filters to store query when available
if (filter.queryExpression) query.filter = filter.queryExpression;
return this.store.query(modelName, query);
}
}
@@ -1,53 +0,0 @@
import Service from '@ember/service';
import { inject as service } from '@ember/service';
class ResourceFilters {
buildQuery(resourceFilters) {
const filters = this._buildFilters(resourceFilters);
return this._joinFilters(filters, { inclusive: false });
}
/**
* Iterate through filter config to generate array of filter strings
* @param {object} config
*/
_buildFilters(config) {
let filters = [];
for (const key in config) {
filters.push(this._generateQuery(`/item/${key}`, config[`${key}`]));
}
return filters;
}
/**
* For a single filter, build filter query
* @param {string} selector
* @param {*} values
*/
_generateQuery(selector, values) {
// Coerce single string filters into an array for easy mapping
if (!Array.isArray(values)) values = [values];
const filters = values.map((value) => `"${selector}" == "${value}"`);
return this._joinFilters(filters, { inclusive: true });
}
/**
* Combine filter strings
* @param {array} filters
* @param {object} inclusive
*/
_joinFilters(filters, { inclusive }) {
return filters.join(inclusive ? ' or ' : ' and ');
}
}
export default class ResourceFilteringStoreService extends Service {
@service store;
queryBy(modelName, resourceParams, resourceFilters) {
const filterQuery = new ResourceFilters().buildQuery(resourceFilters);
// Apply filters to store query when available
if (filterQuery) resourceParams.filter = filterQuery;
return this.store.query(modelName, resourceParams);
}
}
@@ -0,0 +1 @@
export { default } from 'api/services/resource-filter-store';
@@ -1 +0,0 @@
export { default } from 'api/services/resource-filtering-store';
@@ -0,0 +1,18 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { ResourceFilter } from 'api/services/resource-filter-store';
module('Unit | Service | resource-filter-store', function (hooks) {
setupTest(hooks);
test('it exports a ResourceFilter class that converts filterObjects to query expressions', function (assert) {
assert.ok(ResourceFilter);
const filter = new ResourceFilter({
type: 'oidc',
status: ['active', 'pending'],
});
const expected =
'("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending")';
assert.equal(filter.queryExpression, expected);
});
});
@@ -1,29 +0,0 @@
import { module, test, skip } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Service | resource-filtering-store', function (hooks) {
setupTest(hooks);
let service, store;
hooks.beforeEach(function () {
service = this.owner.lookup('service:resource-filtering-store');
store = this.owner.lookup('service:store');
store.createRecord('session', {
id: 's_1',
scope: {
id: '123',
},
});
});
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
assert.ok(service);
});
// TODO: Is it possible to capture mirage API requests in this test?
skip('it supports query filters', function (assert) {
service.queryBy('session', {}, { scope_id: '123' });
assert.ok(service);
});
});
@@ -13,6 +13,7 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
@service ipc;
@service session;
@service resourceFilterStore;
// =attributes
@@ -52,8 +53,7 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
const { id: scope_id } = this.modelFor('scopes.scope');
const { user_id } = this.session.data.authenticated;
await this.store.query('target', { recursive: true, scope_id });
return await this.store.query('session', {
filter: `"/item/user_id" == "${user_id}"`,
return await this.resourceFilterStore.queryBy('session', { user_id }, {
recursive: true,
scope_id
});