feat: 🎸 Add custom sort support to sql parser (#2959)
* feat: 🎸 Add custom sort support to sql parser * test: 💍 fix column numner * refactor: 💡 apply pr feedback * fix: 🐛 use every method for checking attrs * refactor: 💡 PR feedback + ensure default sort * refactor: 💡 Consolidate attributes field when sorting ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17441 --------- Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
This commit is contained in:
1 parent
eab93cf6c5
commit
d414df4ca4
29 files changed
+333
-260
No files matched your search
@@ -28,6 +28,13 @@ export const modelMapping = {
|
||||
scope_id: 'scope.scope_id',
|
||||
created_time: 'created_time',
|
||||
},
|
||||
group: {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
scope_id: 'scope.scope_id',
|
||||
created_time: 'created_time',
|
||||
},
|
||||
};
|
||||
|
||||
// A list of tables that we support searching using FTS5 in SQLite.
|
||||
|
||||
@@ -41,11 +41,11 @@ function getSortableValue(schema, record, attribute) {
|
||||
|
||||
export const sortResults = (results, { querySort, schema }) => {
|
||||
querySort = querySort ?? {};
|
||||
const sortAttribute = querySort.attribute || SORT_DEFAULT_ATTRIBUTE;
|
||||
const sortAttribute = querySort.attributes?.[0] || 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
|
||||
sortAttribute === SORT_DEFAULT_ATTRIBUTE
|
||||
? SORT_DIRECTION_DESCENDING
|
||||
: SORT_DIRECTION_ASCENDING;
|
||||
const sortDirection = querySort.direction || defaultSortDirection;
|
||||
@@ -58,10 +58,44 @@ export const sortResults = (results, { querySort, schema }) => {
|
||||
throw new Error('Invalid sort direction');
|
||||
}
|
||||
|
||||
// Execute custom sort function if one is provided.
|
||||
if (querySort.sortFunction) {
|
||||
const sortAttributeDataType = schema.attributes.get(sortAttribute)?.type;
|
||||
|
||||
const sortFunction =
|
||||
sortFunctions[sortAttributeDataType] ?? sortFunctions.string;
|
||||
|
||||
// Execute custom sort for multiple attributes provided.
|
||||
if (querySort.attributes?.length > 1) {
|
||||
const { attributes } = querySort;
|
||||
return results.toSorted((a, b) => {
|
||||
const sortResult = querySort.sortFunction(a, b);
|
||||
let sortValueA, sortValueB;
|
||||
|
||||
attributes.forEach((attr) => {
|
||||
const valueA = getSortableValue(schema, a, attr);
|
||||
const valueB = getSortableValue(schema, b, attr);
|
||||
|
||||
if (valueA && !sortValueA) {
|
||||
sortValueA = valueA;
|
||||
}
|
||||
if (valueB && !sortValueB) {
|
||||
sortValueB = valueB;
|
||||
}
|
||||
});
|
||||
const sortResult = sortFunction(sortValueA, sortValueB);
|
||||
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||
? sortResult
|
||||
: -1 * sortResult;
|
||||
});
|
||||
}
|
||||
// Execute custom sort for attribute map provided.
|
||||
if (querySort.customSort) {
|
||||
const { attributeMap } = querySort.customSort;
|
||||
return results.toSorted((a, b) => {
|
||||
const sortValueA = getSortableValue(schema, a, sortAttribute);
|
||||
const sortValueB = getSortableValue(schema, b, sortAttribute);
|
||||
const sortResult = sortFunction(
|
||||
attributeMap[sortValueA],
|
||||
attributeMap[sortValueB],
|
||||
);
|
||||
|
||||
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||
? sortResult
|
||||
@@ -69,11 +103,6 @@ export const sortResults = (results, { querySort, schema }) => {
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -33,18 +33,7 @@ export function generateSQLExpressions(
|
||||
addFilterConditions(filters, parameters, conditions);
|
||||
addSearchConditions(search, resource, parameters, conditions);
|
||||
|
||||
const { attribute, direction } = sort;
|
||||
let orderByClause = '';
|
||||
if (attribute) {
|
||||
// We have to check if the attribute is valid for the resource
|
||||
// as we can't use parameterized queries for ORDER BY
|
||||
const validAttributes = Object.keys(modelMapping[resource]);
|
||||
if (validAttributes.includes(attribute)) {
|
||||
orderByClause = `ORDER BY ${attribute} ${direction === 'desc' ? 'DESC' : 'ASC'}`;
|
||||
}
|
||||
} else if (modelMapping[resource]?.created_time) {
|
||||
orderByClause = `ORDER BY created_time DESC`;
|
||||
}
|
||||
const orderByClause = constructOrderByClause(resource, sort);
|
||||
|
||||
const whereClause = conditions.length ? `WHERE ${and(conditions)}` : '';
|
||||
|
||||
@@ -53,7 +42,7 @@ export function generateSQLExpressions(
|
||||
parameters.push(pageSize, (page - 1) * pageSize);
|
||||
}
|
||||
|
||||
const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM ${resource}`;
|
||||
const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${resource}"`;
|
||||
|
||||
return {
|
||||
// Replace any empty newlines or leading whitespace on each line to be consistent with formatting
|
||||
@@ -145,6 +134,50 @@ function addSearchConditions(search, resource, parameters, conditions) {
|
||||
conditions.push(searchConditions);
|
||||
}
|
||||
|
||||
function constructOrderByClause(resource, sort) {
|
||||
const defaultOrderByClause = 'ORDER BY created_time DESC';
|
||||
|
||||
const { attributes, customSort, direction, isCoalesced } = sort;
|
||||
const sortDirection = direction === 'desc' ? 'DESC' : 'ASC';
|
||||
|
||||
// We have to check if the attributes are valid for the resource
|
||||
// as we can't use parameterized queries for ORDER BY
|
||||
const validAttributes = Object.keys(modelMapping[resource] ?? {});
|
||||
if (attributes?.some((attr) => !validAttributes.includes(attr))) {
|
||||
return modelMapping[resource]?.created_time ? defaultOrderByClause : '';
|
||||
}
|
||||
|
||||
if (customSort?.attributeMap) {
|
||||
const whenClauses = Object.keys(customSort.attributeMap).reduce(
|
||||
(acc, key) => {
|
||||
return acc + `WHEN '${key}' THEN '${customSort.attributeMap[key]}' `;
|
||||
},
|
||||
'',
|
||||
);
|
||||
return `ORDER BY CASE ${attributes.join(', ')} ${whenClauses}END ${sortDirection}`;
|
||||
} else if (attributes?.length > 0) {
|
||||
const commaSeparatedVals = attributes.join(', ');
|
||||
|
||||
// In places where `collate nocase` is used, it is to ensure case is ignored on the initial sort.
|
||||
// Then, a sort on the same condition is performed to ensure upper-case strings are given preference in a tie.
|
||||
if (isCoalesced) {
|
||||
return `ORDER BY COALESCE(${attributes.join(', ')}) COLLATE NOCASE ${sortDirection}, COALESCE(${commaSeparatedVals}) ${sortDirection}`;
|
||||
}
|
||||
|
||||
const attributesWithNoCollate = attributes
|
||||
.map((attr) => `${attr} COLLATE NOCASE ${sortDirection}`)
|
||||
.join(', ');
|
||||
const attributesWithDirection = attributes
|
||||
.map((attr) => `${attr} ${sortDirection}`)
|
||||
.join(', ');
|
||||
return `ORDER BY ${attributesWithNoCollate}, ${attributesWithDirection}`;
|
||||
} else if (modelMapping[resource]?.created_time) {
|
||||
return defaultOrderByClause;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Comparison Operators
|
||||
const OPERATORS = {
|
||||
equals: ' = ?',
|
||||
|
||||
@@ -14,7 +14,7 @@ const createTargetTables = `
|
||||
CREATE TABLE IF NOT EXISTS target (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
address TEXT,
|
||||
scope_id TEXT NOT NULL,
|
||||
@@ -105,6 +105,16 @@ CREATE TABLE IF NOT EXISTS token (
|
||||
${createTargetTables}
|
||||
${createAliasTables}
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "group" (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT,
|
||||
description TEXT,
|
||||
scope_id TEXT NOT NULL,
|
||||
created_time TEXT NOT NULL,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_group_scope_id_created_time ON "group"(scope_id, created_time DESC);
|
||||
|
||||
COMMIT;`;
|
||||
|
||||
export const INSERT_STATEMENTS = (resource, items, modelMapping) => {
|
||||
@@ -121,8 +131,8 @@ export const INSERT_STATEMENTS = (resource, items, modelMapping) => {
|
||||
const numColumns = Array(columns.length + 1);
|
||||
const placeholders = `(${numColumns.fill('?').join(', ')})`;
|
||||
|
||||
return `REPLACE INTO ${resource} (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`;
|
||||
return `REPLACE INTO "${resource}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`;
|
||||
};
|
||||
|
||||
export const DELETE_STATEMENT = (resource, ids) =>
|
||||
`DELETE FROM ${resource} WHERE id IN (${ids.map(() => '?').join(',')})`;
|
||||
`DELETE FROM "${resource}" WHERE id IN (${ids.map(() => '?').join(',')})`;
|
||||
@@ -262,7 +262,7 @@ module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||
);
|
||||
|
||||
const results = await store.query('target', {
|
||||
query: { sort: { attribute: 'name' } },
|
||||
query: { sort: { attributes: ['name'] } },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -292,10 +292,10 @@ module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||
);
|
||||
|
||||
const resultsDesc = await store.query('alias', {
|
||||
query: { sort: { attribute: 'name', direction: 'desc' } },
|
||||
query: { sort: { attributes: ['name'], direction: 'desc' } },
|
||||
});
|
||||
const resultsAsc = await store.query('alias', {
|
||||
query: { sort: { attribute: 'name', direction: 'asc' } },
|
||||
query: { sort: { attributes: ['name'], direction: 'asc' } },
|
||||
});
|
||||
assert.deepEqual(
|
||||
resultsDesc.map(({ name }) => name),
|
||||
@@ -313,16 +313,11 @@ module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||
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 nameMap = {
|
||||
alias3: 'Alpha',
|
||||
alias4: 'Beta',
|
||||
alias1: 'Delta',
|
||||
alias2: 'Epsilon',
|
||||
};
|
||||
|
||||
const shuffledAliases = faker.helpers.shuffle([
|
||||
@@ -340,10 +335,22 @@ module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||
);
|
||||
|
||||
const resultsDesc = await store.query('alias', {
|
||||
query: { sort: { sortFunction: sortName, direction: 'desc' } },
|
||||
query: {
|
||||
sort: {
|
||||
attributes: ['name'],
|
||||
customSort: { attributeMap: nameMap },
|
||||
direction: 'desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
const resultsAsc = await store.query('alias', {
|
||||
query: { sort: { sortFunction: sortName, direction: 'asc' } },
|
||||
query: {
|
||||
sort: {
|
||||
attributes: ['name'],
|
||||
customSort: { attributeMap: nameMap },
|
||||
direction: 'asc',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
resultsDesc.map(({ name }) => name),
|
||||
|
||||
@@ -303,7 +303,7 @@ module('Unit | Handler | sqlite-handler', function (hooks) {
|
||||
);
|
||||
|
||||
const results = await store.query('target', {
|
||||
query: { sort: { attribute: 'name' } },
|
||||
query: { sort: { attributes: ['name'] } },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -345,10 +345,10 @@ module('Unit | Handler | sqlite-handler', function (hooks) {
|
||||
);
|
||||
|
||||
const resultsDesc = await store.query('target', {
|
||||
query: { sort: { attribute: 'name', direction: 'desc' } },
|
||||
query: { sort: { attributes: ['name'], direction: 'desc' } },
|
||||
});
|
||||
const resultsAsc = await store.query('target', {
|
||||
query: { sort: { attribute: 'name', direction: 'asc' } },
|
||||
query: { sort: { attributes: ['name'], direction: 'asc' } },
|
||||
});
|
||||
assert.deepEqual(
|
||||
resultsDesc.map(({ name }) => name),
|
||||
|
||||
@@ -37,7 +37,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
]);
|
||||
|
||||
const querySort = { attribute: 'name' };
|
||||
const querySort = { attributes: ['name'] };
|
||||
|
||||
// Note: `name` is not defined as a schema attribute but exists on the record
|
||||
const schema = { attributes: new Map() };
|
||||
@@ -55,7 +55,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
});
|
||||
|
||||
const querySort = {
|
||||
attribute: 'name',
|
||||
attributes: ['name'],
|
||||
direction: 'invalid sort direction',
|
||||
};
|
||||
const schema = { attributes: new Map() };
|
||||
@@ -119,7 +119,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySort = { attribute: 'id' };
|
||||
const querySort = { attributes: ['id'] };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('id', { type: 'string' });
|
||||
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||
@@ -152,7 +152,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySort = { attribute: 'name' };
|
||||
const querySort = { attributes: ['name'] };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('name', { type: 'string' });
|
||||
const sortedResults = sortResults(shuffledResults, { querySort, schema });
|
||||
@@ -182,8 +182,8 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySortAsc = { attribute: 'name', direction: 'asc' };
|
||||
const querySortDesc = { attribute: 'name', direction: 'desc' };
|
||||
const querySortAsc = { attributes: ['name'], direction: 'asc' };
|
||||
const querySortDesc = { attributes: ['name'], direction: 'desc' };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('name', { type: 'string' });
|
||||
const sortedResultsAsc = sortResults(shuffledResults, {
|
||||
@@ -227,8 +227,8 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result04,
|
||||
result05,
|
||||
]);
|
||||
const querySortAsc = { attribute: 'name', direction: 'asc' };
|
||||
const querySortDesc = { attribute: 'name', direction: 'desc' };
|
||||
const querySortAsc = { attributes: ['name'], direction: 'asc' };
|
||||
const querySortDesc = { attributes: ['name'], direction: 'desc' };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('name', { type: 'string' });
|
||||
|
||||
@@ -272,7 +272,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySort = { attribute: 'date' };
|
||||
const querySort = { attributes: ['date'] };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('date', { type: 'date' });
|
||||
|
||||
@@ -304,8 +304,8 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySortAsc = { attribute: 'date', direction: 'asc' };
|
||||
const querySortDesc = { attribute: 'date', direction: 'desc' };
|
||||
const querySortAsc = { attributes: ['date'], direction: 'asc' };
|
||||
const querySortDesc = { attributes: ['date'], direction: 'desc' };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('date', { type: 'date' });
|
||||
|
||||
@@ -350,7 +350,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySort = { attribute: 'sessions' };
|
||||
const querySort = { attributes: ['sessions'] };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('sessions', { type: 'number' });
|
||||
|
||||
@@ -382,8 +382,8 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySortAsc = { attribute: 'sessions', direction: 'asc' };
|
||||
const querySortDesc = { attribute: 'sessions', direction: 'desc' };
|
||||
const querySortAsc = { attributes: ['sessions'], direction: 'asc' };
|
||||
const querySortDesc = { attributes: ['sessions'], direction: 'desc' };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('sessions', { type: 'number' });
|
||||
|
||||
@@ -419,7 +419,7 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySort = { attribute: 'active' };
|
||||
const querySort = { attributes: ['active'] };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('active', { type: 'boolean' });
|
||||
|
||||
@@ -442,8 +442,8 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
result03,
|
||||
result04,
|
||||
]);
|
||||
const querySortAsc = { attribute: 'active', direction: 'asc' };
|
||||
const querySortDesc = { attribute: 'active', direction: 'desc' };
|
||||
const querySortAsc = { attributes: ['active'], direction: 'asc' };
|
||||
const querySortDesc = { attributes: ['active'], direction: 'desc' };
|
||||
const schema = { attributes: new Map() };
|
||||
schema.attributes.set('active', { type: 'boolean' });
|
||||
|
||||
@@ -482,32 +482,39 @@ module('Unit | Utility | sortResults', function (hooks) {
|
||||
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 nameMap = {
|
||||
target3: 'Alpha',
|
||||
target4: 'Beta',
|
||||
target1: 'Delta',
|
||||
target2: 'Epsilon',
|
||||
};
|
||||
|
||||
const schema = { attributes: new Map() };
|
||||
|
||||
test.each(
|
||||
'it sorts by `name` using custom sorting function',
|
||||
{
|
||||
'ascending (default)': {
|
||||
querySort: { sortFunction },
|
||||
querySort: {
|
||||
attributes: ['name'],
|
||||
customSort: { attributeMap: nameMap },
|
||||
},
|
||||
expectedResults: ['target3', 'target4', 'target1', 'target2'],
|
||||
},
|
||||
descending: {
|
||||
querySort: { sortFunction, direction: 'desc' },
|
||||
querySort: {
|
||||
attributes: ['name'],
|
||||
customSort: { attributeMap: nameMap },
|
||||
direction: 'desc',
|
||||
},
|
||||
expectedResults: ['target2', 'target1', 'target4', 'target3'],
|
||||
},
|
||||
ascending: {
|
||||
querySort: { sortFunction, direction: 'asc' },
|
||||
querySort: {
|
||||
attributes: ['name'],
|
||||
customSort: { attributeMap: nameMap },
|
||||
direction: 'asc',
|
||||
},
|
||||
expectedResults: ['target3', 'target4', 'target1', 'target2'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
SELECT * FROM "target"
|
||||
WHERE rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
@@ -44,7 +44,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM token
|
||||
SELECT * FROM "token"
|
||||
WHERE (id = ?)`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, ['tokenKey']);
|
||||
@@ -59,8 +59,8 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT count(*) as total FROM target
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
SELECT count(*) as total FROM "target"
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
});
|
||||
@@ -160,7 +160,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
SELECT * FROM "target"
|
||||
${expectedWhereClause}
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
@@ -176,15 +176,55 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
],
|
||||
function (assert, [direction, expectedDirection]) {
|
||||
const query = {
|
||||
sort: { attribute: 'name', direction },
|
||||
sort: { attributes: ['name'], direction },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
ORDER BY name ${expectedDirection}`.removeExtraWhiteSpace(),
|
||||
SELECT * FROM "target"
|
||||
ORDER BY name COLLATE NOCASE ${expectedDirection}, name ${expectedDirection}`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
},
|
||||
);
|
||||
|
||||
test.each(
|
||||
'it generates sort order clause correctly',
|
||||
{
|
||||
'sort on multiple attributes and coalesced': {
|
||||
sort: {
|
||||
attributes: ['name', 'id'],
|
||||
isCoalesced: true,
|
||||
},
|
||||
expectedOrderByClause: `ORDER BY COALESCE(name, id) COLLATE NOCASE DESC, COALESCE(name, id) DESC`,
|
||||
},
|
||||
'sort on multiple attributes': {
|
||||
sort: {
|
||||
attributes: ['name', 'id'],
|
||||
},
|
||||
expectedOrderByClause: `ORDER BY name COLLATE NOCASE DESC, id COLLATE NOCASE DESC, name DESC, id DESC`,
|
||||
},
|
||||
'sort on mapped attributes': {
|
||||
sort: {
|
||||
attributes: ['type'],
|
||||
customSort: { attributeMap: { ssh: 'SSH', tcp: 'Generic TCP' } },
|
||||
},
|
||||
expectedOrderByClause: `ORDER BY CASE type WHEN 'ssh' THEN 'SSH' WHEN 'tcp' THEN 'Generic TCP' END DESC`,
|
||||
},
|
||||
},
|
||||
function (assert, { sort, expectedOrderByClause }) {
|
||||
const query = {
|
||||
sort: { ...sort, direction: 'desc' },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM "target"
|
||||
${expectedOrderByClause}`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
},
|
||||
@@ -206,7 +246,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
SELECT * FROM "target"
|
||||
ORDER BY created_time DESC
|
||||
LIMIT ? OFFSET ?`.removeExtraWhiteSpace(),
|
||||
);
|
||||
@@ -230,7 +270,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
SELECT * FROM "target"
|
||||
WHERE (id != ? AND id != ?) AND (status = ? OR status = ?) AND rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
@@ -254,7 +294,7 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
},
|
||||
created_time: [{ gte: date }],
|
||||
},
|
||||
sort: { attribute: 'name', direction: 'desc' },
|
||||
sort: { attributes: ['name'], direction: 'desc' },
|
||||
};
|
||||
|
||||
const { sql, parameters } = generateSQLExpressions('target', query, {
|
||||
@@ -266,9 +306,9 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
`
|
||||
SELECT data FROM target
|
||||
SELECT data FROM "target"
|
||||
WHERE (type = ?) AND (status = ? OR status = ?) AND (created_time >= ?) AND rowid IN (SELECT rowid FROM target_fts WHERE target_fts MATCH ?)
|
||||
ORDER BY name DESC
|
||||
ORDER BY name COLLATE NOCASE DESC, name DESC
|
||||
LIMIT ? OFFSET ?`.removeExtraWhiteSpace(),
|
||||
);
|
||||
|
||||
@@ -290,19 +330,38 @@ module('Unit | Utility | sqlite-query', function (hooks) {
|
||||
emptySearch: { search: '' },
|
||||
emptyFilter: { filters: {} },
|
||||
emptyFilterAttributes: { filters: { type: [] } },
|
||||
invalidSortAttribute: { attribute: 'test', direction: 'asc' },
|
||||
invalidSortAttribute: {
|
||||
sort: { attributes: ['test'], direction: 'asc' },
|
||||
},
|
||||
invalidCoalescedSortAttribute: {
|
||||
sort: {
|
||||
attributes: ['id', 'test'],
|
||||
direction: 'asc',
|
||||
isCoalesced: true,
|
||||
},
|
||||
},
|
||||
onlyPage: { page: 10 },
|
||||
onlyPageSize: { pageSize: 5 },
|
||||
},
|
||||
function (assert, query) {
|
||||
const { sql, parameters } = generateSQLExpressions('target', query);
|
||||
const { sql: targetSql, parameters: targetParameters } =
|
||||
generateSQLExpressions('target', query);
|
||||
assert.strictEqual(
|
||||
sql,
|
||||
targetSql,
|
||||
`
|
||||
SELECT * FROM target
|
||||
SELECT * FROM "target"
|
||||
ORDER BY created_time DESC`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(parameters, []);
|
||||
assert.deepEqual(targetParameters, []);
|
||||
|
||||
const { sql: resourceSql, parameters: resourceParameters } =
|
||||
generateSQLExpressions('resource', query);
|
||||
assert.strictEqual(
|
||||
resourceSql,
|
||||
`
|
||||
SELECT * FROM "resource"`.removeExtraWhiteSpace(),
|
||||
);
|
||||
assert.deepEqual(resourceParameters, []);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -75,7 +75,7 @@ export default class ScopesScopeAliasesIndexRoute extends Route {
|
||||
})
|
||||
) {
|
||||
const sort = {
|
||||
attribute: sortAttribute,
|
||||
attributes: [sortAttribute],
|
||||
direction: sortDirection,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeAuthMethodsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -83,8 +82,12 @@ export default class ScopesScopeAuthMethodsIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
types.forEach((type) => {
|
||||
filters.type.push({ equals: type });
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeCredentialStoresIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -80,8 +79,12 @@ export default class ScopesScopeCredentialStoresIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let credentialStores;
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeGroupsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -68,8 +67,12 @@ export default class ScopesScopeGroupsIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let groups = [];
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeHostCatalogsIndexRoute extends Route {
|
||||
// =services
|
||||
@@ -73,8 +72,12 @@ export default class ScopesScopeHostCatalogsIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let hostCatalogs;
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
import { GRANT_SCOPE_THIS } from 'api/models/role';
|
||||
|
||||
export default class ScopesScopeRolesIndexRoute extends Route {
|
||||
// =attributes
|
||||
@@ -33,21 +31,6 @@ export default class ScopesScopeRolesIndexRoute extends Route {
|
||||
},
|
||||
};
|
||||
|
||||
customSortFunction = {
|
||||
name: sortNameWithIdFallback,
|
||||
grant_scope_ids: (recordA, recordB) => {
|
||||
const a = recordA.attributes?.grant_scope_ids.includes(GRANT_SCOPE_THIS);
|
||||
const b = recordB.attributes?.grant_scope_ids.includes(GRANT_SCOPE_THIS);
|
||||
if (a === b) {
|
||||
return 0;
|
||||
} else if (a) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@@ -84,10 +67,14 @@ export default class ScopesScopeRolesIndexRoute extends Route {
|
||||
scope_id: [{ equals: scope_id }],
|
||||
};
|
||||
|
||||
const sortFunction = this.customSortFunction[sortAttribute];
|
||||
const sort = sortFunction
|
||||
? { sortFunction, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let roles;
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeScopesIndexRoute extends Route {
|
||||
// =attributes
|
||||
@@ -70,8 +69,12 @@ export default class ScopesScopeScopesIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
const subScopes = await this.store.query('scope', {
|
||||
scope_id,
|
||||
|
||||
@@ -71,17 +71,6 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route {
|
||||
return this.retrieveData.perform({ ...params, useDebounce });
|
||||
}
|
||||
|
||||
sortState = (recordA, recordB) => {
|
||||
const stateMap = {
|
||||
[STATE_SESSION_RECORDING_AVAILABLE]: this.intl.t('states.completed'),
|
||||
[STATE_SESSION_RECORDING_STARTED]: this.intl.t('states.recording'),
|
||||
[STATE_SESSION_RECORDING_UNKNOWN]: this.intl.t('states.failed'),
|
||||
};
|
||||
return String(stateMap[recordA.attributes.state]).localeCompare(
|
||||
String(stateMap[recordB.attributes.state]),
|
||||
);
|
||||
};
|
||||
|
||||
retrieveData = restartableTask(
|
||||
async ({
|
||||
search,
|
||||
@@ -122,10 +111,20 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route {
|
||||
filters['create_time_values.target.id'].push({ equals: target });
|
||||
});
|
||||
|
||||
const stateMap = {
|
||||
[STATE_SESSION_RECORDING_AVAILABLE]: this.intl.t('states.completed'),
|
||||
[STATE_SESSION_RECORDING_STARTED]: this.intl.t('states.recording'),
|
||||
[STATE_SESSION_RECORDING_UNKNOWN]: this.intl.t('states.failed'),
|
||||
};
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'state'
|
||||
? { sortFunction: this.sortState, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute],
|
||||
customSort: { attributeMap: stateMap },
|
||||
direction: sortDirection,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
if (
|
||||
this.can.can('list scope', scope, {
|
||||
|
||||
@@ -98,7 +98,7 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
|
||||
});
|
||||
|
||||
const sort = {
|
||||
attribute: sortAttribute,
|
||||
attributes: [sortAttribute],
|
||||
direction: sortDirection,
|
||||
};
|
||||
|
||||
|
||||
@@ -64,16 +64,6 @@ export default class ScopesScopeTargetsIndexRoute extends Route {
|
||||
return this.retrieveData.perform({ ...params, useDebounce });
|
||||
}
|
||||
|
||||
sortType = (recordA, recordB) => {
|
||||
const typeMap = {
|
||||
[TYPE_TARGET_SSH]: this.intl.t('resources.target.types.ssh'),
|
||||
[TYPE_TARGET_TCP]: this.intl.t('resources.target.types.tcp'),
|
||||
};
|
||||
return String(typeMap[recordA.attributes.type]).localeCompare(
|
||||
String(typeMap[recordB.attributes.type]),
|
||||
);
|
||||
};
|
||||
|
||||
retrieveData = restartableTask(
|
||||
async ({
|
||||
search,
|
||||
@@ -117,10 +107,19 @@ export default class ScopesScopeTargetsIndexRoute extends Route {
|
||||
this.addActiveSessionFilters(filters, availableSessions, sessions);
|
||||
}
|
||||
|
||||
const typeMap = {
|
||||
[TYPE_TARGET_SSH]: this.intl.t('resources.target.types.ssh'),
|
||||
[TYPE_TARGET_TCP]: this.intl.t('resources.target.types.tcp'),
|
||||
};
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'type'
|
||||
? { sortFunction: this.sortType, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute],
|
||||
customSort: { attributeMap: typeMap },
|
||||
direction: sortDirection,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let targets;
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
|
||||
export default class ScopesScopeUsersIndexRoute extends Route {
|
||||
// =attributes
|
||||
@@ -71,8 +70,12 @@ export default class ScopesScopeUsersIndexRoute extends Route {
|
||||
|
||||
const sort =
|
||||
sortAttribute === 'name'
|
||||
? { sortFunction: sortNameWithIdFallback, direction: sortDirection }
|
||||
: { attribute: sortAttribute, direction: sortDirection };
|
||||
? {
|
||||
attributes: [sortAttribute, 'id'],
|
||||
direction: sortDirection,
|
||||
isCoalesced: true,
|
||||
}
|
||||
: { attributes: [sortAttribute], direction: sortDirection };
|
||||
|
||||
let users;
|
||||
let totalItems = 0;
|
||||
|
||||
@@ -59,10 +59,7 @@
|
||||
>
|
||||
{{t 'form.name.label'}}
|
||||
</H.ThSort>
|
||||
<H.ThSort
|
||||
@onClickSort={{fn H.setSortBy 'grant_scope_ids'}}
|
||||
@sortOrder={{if (eq 'grant_scope_ids' H.sortBy) H.sortOrder}}
|
||||
>{{t 'resources.role.titles.grants-applied'}}</H.ThSort>
|
||||
<H.Th>{{t 'resources.role.titles.grants-applied'}}</H.Th>
|
||||
<H.ThSort
|
||||
@onClickSort={{fn H.setSortBy 'id'}}
|
||||
@sortOrder={{if (eq 'id' H.sortBy) H.sortOrder}}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export function sortNameWithIdFallback(recordA, recordB) {
|
||||
let a = recordA.attributes.name;
|
||||
let b = recordB.attributes.name;
|
||||
a = a ? a : recordA.id;
|
||||
b = b ? b : recordB.id;
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click, fillIn } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { Response } from 'miragejs';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
@@ -16,7 +17,9 @@ import * as commonSelectors from 'admin/tests/helpers/selectors';
|
||||
module('Acceptance | groups | create', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
let groupsCount;
|
||||
|
||||
const instances = {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import * as commonSelectors from 'admin/tests/helpers/selectors';
|
||||
@@ -22,6 +23,7 @@ import { faker } from '@faker-js/faker';
|
||||
module('Acceptance | groups | list', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
const SEARCH_INPUT_SELECTOR = '.search-filtering [type="search"]';
|
||||
|
||||
@@ -242,14 +242,6 @@ module('Acceptance | roles | list', function (hooks) {
|
||||
expectedAscendingSort: ['Alpha', 'Beta', 'Delta', 'Epsilon', 'Gamma'],
|
||||
column: 1,
|
||||
},
|
||||
'on grants applied': {
|
||||
attribute: {
|
||||
key: 'grant_scope_ids',
|
||||
values: [[GRANT_SCOPE_THIS], [GRANT_SCOPE_THIS], [], [], []],
|
||||
},
|
||||
expectedAscendingSort: ['No', 'No', 'No', 'Yes', 'Yes'],
|
||||
column: 2,
|
||||
},
|
||||
'on id': {
|
||||
attribute: {
|
||||
key: 'id',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click, findAll } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { Response } from 'miragejs';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
@@ -17,6 +18,7 @@ import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
module('Acceptance | roles | principals', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
|
||||
const instances = {
|
||||
|
||||
@@ -16,7 +16,6 @@ import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target';
|
||||
import { STATUS_SESSION_ACTIVE } from 'api/models/session';
|
||||
@@ -30,7 +29,6 @@ module('Acceptance | targets | list', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
|
||||
const NAME_VALUES_ARRAY = ['Alpha', 'Beta', 'Delta', 'Epsilon', 'Gamma'];
|
||||
const ID_VALUES_ARRAY = ['i_0001', 'i_0010', 'i_0100', 'i_1000', 'i_10000'];
|
||||
@@ -397,15 +395,14 @@ module('Acceptance | targets | list', function (hooks) {
|
||||
expectedAscendingSort: NAME_VALUES_ARRAY,
|
||||
column: 1,
|
||||
},
|
||||
// TODO: Fix this custom sort
|
||||
// 'on type': {
|
||||
// attribute: {
|
||||
// key: 'type',
|
||||
// values: [TYPE_TARGET_SSH, TYPE_TARGET_TCP, TYPE_TARGET_SSH],
|
||||
// },
|
||||
// expectedAscendingSort: ['Generic TCP', 'SSH', 'SSH'],
|
||||
// column: 2,
|
||||
// },
|
||||
'on type': {
|
||||
attribute: {
|
||||
key: 'type',
|
||||
values: [TYPE_TARGET_SSH, TYPE_TARGET_TCP, TYPE_TARGET_SSH],
|
||||
},
|
||||
expectedAscendingSort: ['Generic TCP', 'SSH', 'SSH'],
|
||||
column: 2,
|
||||
},
|
||||
'on id': {
|
||||
attribute: {
|
||||
key: 'id',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { waitUntil, visit } from '@ember/test-helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
@@ -14,6 +15,7 @@ import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
module('Unit | Controller | scopes/scope/groups/index', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
setupIndexedDb(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { visit } from '@ember/test-helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
|
||||
module(
|
||||
@@ -14,6 +15,7 @@ module(
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupSqlite(hooks);
|
||||
|
||||
let store;
|
||||
let controller;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { sortNameWithIdFallback } from 'admin/utils/sort-name-with-id-fallback';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
function createMockRecord(id, attributes = {}) {
|
||||
return {
|
||||
id,
|
||||
attributes,
|
||||
};
|
||||
}
|
||||
|
||||
module('Unit | Utility | sort-name-with-id-fallback', function () {
|
||||
test.each(
|
||||
'sort on name with id fallback',
|
||||
[
|
||||
{
|
||||
recordA: createMockRecord('id_01'),
|
||||
recordB: createMockRecord('id_01'),
|
||||
expectedResult: 0,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_02'),
|
||||
recordB: createMockRecord('id_01'),
|
||||
expectedResult: 1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01'),
|
||||
recordB: createMockRecord('id_02'),
|
||||
expectedResult: -1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01', { name: 'Alpha' }),
|
||||
recordB: createMockRecord('id_02'),
|
||||
expectedResult: -1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01'),
|
||||
recordB: createMockRecord('id_02', { name: 'Alpha' }),
|
||||
expectedResult: 1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01', { name: 'Alpha' }),
|
||||
recordB: createMockRecord('id_02', { name: 'Beta' }),
|
||||
expectedResult: -1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01', { name: 'Beta' }),
|
||||
recordB: createMockRecord('id_02', { name: 'Alpha' }),
|
||||
expectedResult: 1,
|
||||
},
|
||||
{
|
||||
recordA: createMockRecord('id_01', { name: 'Alpha' }),
|
||||
recordB: createMockRecord('id_02', { name: 'Alpha' }),
|
||||
expectedResult: 0,
|
||||
},
|
||||
],
|
||||
function (assert, input) {
|
||||
const result = sortNameWithIdFallback(input.recordA, input.recordB);
|
||||
|
||||
assert.strictEqual(result, input.expectedResult);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user