mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Added "Name" members filter (#2289)
refs https://github.com/TryGhost/Team/issues/1408 - switched to `@tryghost/nql` packages to get access to latest releases - updated `GET /members` mirage endpoint with a try/catch and explicit logging to make any errors from NQL more visible - added "Name" filter option - has `is`, `contains`, `does not contain`, `starts with`, `ends with` operators - uses a plain text field for the input value - added support for `~`, `-~`, `~^`, and `~$` operators when generating NQL queries from filter definitions
This commit is contained in:
parent
59fb35592d
commit
f65437b14c
6 changed files with 278 additions and 77 deletions
|
@ -1,4 +1,16 @@
|
|||
{{#if (eq @filter.type 'label')}}
|
||||
{{#if (eq @filter.type 'name')}}
|
||||
<input
|
||||
type="text"
|
||||
value={{@filter.value}}
|
||||
class="gh-input"
|
||||
aria-label="Name filter"
|
||||
{{on "input" (fn this.setInputFilterValue @filter)}}
|
||||
{{on "blur" (fn this.updateInputFilterValue @filter)}}
|
||||
{{on "keypress" (fn this.updateInputFilterValueOnEnter @filter)}}
|
||||
data-test-input="members-filter-value"
|
||||
/>
|
||||
|
||||
{{else if (eq @filter.type 'label')}}
|
||||
<GhMemberLabelInput
|
||||
@onChange={{fn this.setLabelsFilterValue @filter}}
|
||||
@onLabelEdit={{@onLabelEdit}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Component from '@glimmer/component';
|
||||
import moment from 'moment';
|
||||
import nql from '@nexes/nql-lang';
|
||||
import nql from '@tryghost/nql-lang';
|
||||
import {TrackedArray} from 'tracked-built-ins';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
@ -9,7 +9,7 @@ import {tracked} from '@glimmer/tracking';
|
|||
|
||||
const FILTER_PROPERTIES = [
|
||||
// Basic
|
||||
// {label: 'Name', name: 'name', group: 'Basic'},
|
||||
{label: 'Name', name: 'name', group: 'Basic', valueType: 'text', feature: 'membersContainsFilters'},
|
||||
// {label: 'Email', name: 'email', group: 'Basic'},
|
||||
// {label: 'Location', name: 'location', group: 'Basic'},
|
||||
{label: 'Label', name: 'label', group: 'Basic', valueType: 'array'},
|
||||
|
@ -60,7 +60,13 @@ const NUMBER_RELATION_OPTIONS = [
|
|||
];
|
||||
|
||||
const FILTER_RELATIONS_OPTIONS = {
|
||||
// name: MATCH_RELATION_OPTIONS,
|
||||
name: [
|
||||
{label: 'is', name: 'is'},
|
||||
{label: 'contains', name: 'contains'},
|
||||
{label: 'does not contain', name: 'does-not-contain'},
|
||||
{label: 'starts with', name: 'starts-with'},
|
||||
{label: 'ends with', name: 'ends-with'}
|
||||
],
|
||||
// email: MATCH_RELATION_OPTIONS,
|
||||
label: MATCH_RELATION_OPTIONS,
|
||||
product: MATCH_RELATION_OPTIONS,
|
||||
|
@ -212,6 +218,9 @@ export default class MembersFilter extends Component {
|
|||
if (filterProperty.valueType === 'array' && filter.value?.length) {
|
||||
const filterValue = '[' + filter.value.join(',') + ']';
|
||||
query += `${filter.type}:${relationStr}${filterValue}+`;
|
||||
} else if (filterProperty.valueType === 'text') {
|
||||
const filterValue = '\'' + filter.value.replace(/'/g, '\\\'') + '\'';
|
||||
query += `${filter.type}:${relationStr}${filterValue}+`;
|
||||
} else if (filterProperty.valueType === 'date') {
|
||||
let filterValue;
|
||||
|
||||
|
@ -285,6 +294,30 @@ export default class MembersFilter extends Component {
|
|||
relation = 'is-or-less';
|
||||
value = nqlValue.$lte;
|
||||
}
|
||||
|
||||
if (nqlValue.$regex !== undefined) {
|
||||
const source = nqlValue.$regex.source;
|
||||
|
||||
if (source.indexOf('^') === 0) {
|
||||
relation = 'starts-with';
|
||||
value = source.substring(1);
|
||||
} else if (source.indexOf('$') === source.length - 1) {
|
||||
relation = 'ends-with';
|
||||
value = source.slice(0, -1);
|
||||
} else {
|
||||
relation = 'contains';
|
||||
value = source;
|
||||
}
|
||||
|
||||
value = value.replace(/\\/g, '');
|
||||
}
|
||||
|
||||
if (nqlValue.$not !== undefined) {
|
||||
relation = 'does-not-contain';
|
||||
value = nqlValue.$not.source;
|
||||
|
||||
value = value.replace(/\\/g, '');
|
||||
}
|
||||
} else {
|
||||
relation = 'is';
|
||||
value = nqlValue;
|
||||
|
@ -337,7 +370,11 @@ export default class MembersFilter extends Component {
|
|||
is: '',
|
||||
'is-not': '-',
|
||||
'is-greater': '>',
|
||||
'is-or-greater': '>='
|
||||
'is-or-greater': '>=',
|
||||
contains: '~',
|
||||
'does-not-contain': '-~',
|
||||
'starts-with': '~^',
|
||||
'ends-with': '~$'
|
||||
};
|
||||
|
||||
return relationMap[relation] || '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import faker from 'faker';
|
||||
import moment from 'moment';
|
||||
import nql from '@nexes/nql';
|
||||
import nql from '@tryghost/nql';
|
||||
import {Response} from 'miragejs';
|
||||
import {extractFilterParam, paginateModelCollection} from '../utils';
|
||||
import {underscore} from '@ember/string';
|
||||
|
@ -73,50 +73,55 @@ export default function mockMembers(server) {
|
|||
let collection = members.all();
|
||||
|
||||
if (filter) {
|
||||
const nqlFilter = nql(filter, {
|
||||
expansions: [
|
||||
{
|
||||
key: 'label',
|
||||
replacement: 'labels.slug'
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
replacement: 'products.slug'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
collection = collection.filter((member) => {
|
||||
const serializedMember = {};
|
||||
|
||||
// mirage model keys match our main model keys so we need to transform
|
||||
// camelCase to underscore to match the filter format
|
||||
Object.keys(member.attrs).forEach((key) => {
|
||||
serializedMember[underscore(key)] = member.attrs[key];
|
||||
try {
|
||||
const nqlFilter = nql(filter, {
|
||||
expansions: [
|
||||
{
|
||||
key: 'label',
|
||||
replacement: 'labels.slug'
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
replacement: 'products.slug'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// similar deal for associated label models
|
||||
serializedMember.labels = [];
|
||||
member.labels.models.forEach((label) => {
|
||||
const serializedLabel = {};
|
||||
Object.keys(label.attrs).forEach((key) => {
|
||||
serializedLabel[underscore(key)] = label.attrs[key];
|
||||
collection = collection.filter((member) => {
|
||||
const serializedMember = {};
|
||||
|
||||
// mirage model keys match our main model keys so we need to transform
|
||||
// camelCase to underscore to match the filter format
|
||||
Object.keys(member.attrs).forEach((key) => {
|
||||
serializedMember[underscore(key)] = member.attrs[key];
|
||||
});
|
||||
serializedMember.labels.push(serializedLabel);
|
||||
});
|
||||
|
||||
// similar deal for associated product models
|
||||
serializedMember.products = [];
|
||||
member.products.models.forEach((product) => {
|
||||
const serializedProduct = {};
|
||||
Object.keys(product.attrs).forEach((key) => {
|
||||
serializedProduct[underscore(key)] = product.attrs[key];
|
||||
// similar deal for associated label models
|
||||
serializedMember.labels = [];
|
||||
member.labels.models.forEach((label) => {
|
||||
const serializedLabel = {};
|
||||
Object.keys(label.attrs).forEach((key) => {
|
||||
serializedLabel[underscore(key)] = label.attrs[key];
|
||||
});
|
||||
serializedMember.labels.push(serializedLabel);
|
||||
});
|
||||
serializedMember.products.push(serializedProduct);
|
||||
});
|
||||
|
||||
return nqlFilter.queryJSON(serializedMember);
|
||||
});
|
||||
// similar deal for associated product models
|
||||
serializedMember.products = [];
|
||||
member.products.models.forEach((product) => {
|
||||
const serializedProduct = {};
|
||||
Object.keys(product.attrs).forEach((key) => {
|
||||
serializedProduct[underscore(key)] = product.attrs[key];
|
||||
});
|
||||
serializedMember.products.push(serializedProduct);
|
||||
});
|
||||
|
||||
return nqlFilter.queryJSON(serializedMember);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err); // eslint-disable-line
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (search) {
|
||||
|
|
|
@ -36,8 +36,6 @@
|
|||
"@glimmer/component": "1.0.4",
|
||||
"@html-next/vertical-collection": "2.1.0",
|
||||
"@joeattardi/emoji-button": "4.6.2",
|
||||
"@nexes/nql": "0.6.0",
|
||||
"@nexes/nql-lang": "0.0.1",
|
||||
"@sentry/ember": "6.16.1",
|
||||
"@tryghost/color-utils": "0.1.9",
|
||||
"@tryghost/helpers": "1.1.59",
|
||||
|
@ -46,6 +44,8 @@
|
|||
"@tryghost/limit-service": "1.0.10",
|
||||
"@tryghost/members-csv": "1.2.6",
|
||||
"@tryghost/mobiledoc-kit": "0.12.5-ghost.1",
|
||||
"@tryghost/nql": "0.9.0",
|
||||
"@tryghost/nql-lang": "0.3.0",
|
||||
"@tryghost/string": "0.1.23",
|
||||
"@tryghost/timezone-data": "0.2.58",
|
||||
"autoprefixer": "9.8.6",
|
||||
|
|
|
@ -19,6 +19,7 @@ describe('Acceptance: Members filtering', function () {
|
|||
beforeEach(async function () {
|
||||
this.server.loadFixtures('configs');
|
||||
this.server.loadFixtures('settings');
|
||||
enableLabsFlag(this.server, 'membersContainsFilters');
|
||||
enableLabsFlag(this.server, 'multipleProducts');
|
||||
|
||||
// test with stripe connected and email turned on
|
||||
|
@ -833,6 +834,152 @@ describe('Acceptance: Members filtering', function () {
|
|||
expect(find('[data-test-table-data="subscriptions.start_date"]')).to.contain.text('a month ago');
|
||||
});
|
||||
|
||||
it('can filter by name', async function () {
|
||||
this.server.create('member', {name: 'test-1'});
|
||||
this.server.create('member', {name: 'test-2'});
|
||||
this.server.create('member', {name: 'tset-1'});
|
||||
this.server.create('member', {name: 'tset-2'});
|
||||
this.server.create('member', {name: 'tset-3'});
|
||||
this.server.create('member', {name: 'hello'});
|
||||
this.server.create('member', {name: 'John O\'Nolan'});
|
||||
this.server.create('member', {name: null});
|
||||
|
||||
await visit('/members');
|
||||
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of initial member rows')
|
||||
.to.equal(8);
|
||||
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
|
||||
const filterSelect = `[data-test-members-filter="0"]`;
|
||||
const typeSelect = `${filterSelect} [data-test-select="members-filter"]`;
|
||||
const operatorSelect = `${filterSelect} [data-test-select="members-filter-operator"]`;
|
||||
const valueInput = `${filterSelect} [data-test-input="members-filter-value"]`;
|
||||
|
||||
expect(find(`${filterSelect} [data-test-select="members-filter"] option[value="name"]`), 'name filter option').to.exist;
|
||||
|
||||
await fillIn(typeSelect, 'name');
|
||||
|
||||
// has the right operators
|
||||
const operatorOptions = findAll(`${operatorSelect} option`);
|
||||
expect(operatorOptions).to.have.length(5);
|
||||
expect(operatorOptions[0]).to.have.value('is');
|
||||
expect(operatorOptions[1]).to.have.value('contains');
|
||||
expect(operatorOptions[2]).to.have.value('does-not-contain');
|
||||
expect(operatorOptions[3]).to.have.value('starts-with');
|
||||
expect(operatorOptions[4]).to.have.value('ends-with');
|
||||
|
||||
// has expected default operator and value
|
||||
expect(find(operatorSelect)).to.have.value('is');
|
||||
expect(find(valueInput)).to.have.value('');
|
||||
|
||||
// can change filter
|
||||
await fillIn(valueInput, 'hello');
|
||||
await blur(valueInput);
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - is "hello"')
|
||||
.to.equal(1);
|
||||
|
||||
// can change operator
|
||||
await fillIn(operatorSelect, 'contains');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - contains "hello"')
|
||||
.to.equal(1);
|
||||
|
||||
// contains query works
|
||||
await fillIn(valueInput, 'test');
|
||||
await blur(valueInput);
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - contains "test"')
|
||||
.to.equal(2);
|
||||
|
||||
// starts with query works
|
||||
await fillIn(operatorSelect, 'starts-with');
|
||||
await fillIn(valueInput, 'tset');
|
||||
await blur(valueInput);
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - starts with "tset"')
|
||||
.to.equal(3);
|
||||
|
||||
// ends with query works
|
||||
await fillIn(operatorSelect, 'ends-with');
|
||||
await fillIn(valueInput, '2');
|
||||
await blur(valueInput);
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - ends with "2"')
|
||||
.to.equal(2);
|
||||
|
||||
// does not contain query works
|
||||
await fillIn(operatorSelect, 'does-not-contain');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - does not contain "2"')
|
||||
.to.equal(6);
|
||||
|
||||
// can query with escaped chars
|
||||
await fillIn(operatorSelect, 'contains');
|
||||
await fillIn(valueInput, `O'Nolan`);
|
||||
await blur(valueInput);
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - contains "O\'Nolan"')
|
||||
.to.equal(1);
|
||||
|
||||
// no duplicate column added (name is included in the "details" column)
|
||||
expect(find('[data-test-table-column="name"]')).to.not.exist;
|
||||
|
||||
// can handle contains operator in URL
|
||||
let filter = encodeURIComponent(`name:~'hello'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL contains "hello"')
|
||||
.to.equal(1);
|
||||
expect(find(operatorSelect)).to.have.value('contains');
|
||||
expect(find(valueInput)).to.have.value('hello');
|
||||
|
||||
// can handle starts-with operator in URL
|
||||
filter = encodeURIComponent(`name:~^'tset'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL starts with "tset"')
|
||||
.to.equal(3);
|
||||
expect(find(operatorSelect)).to.have.value('starts-with');
|
||||
expect(find(valueInput)).to.have.value('tset');
|
||||
|
||||
// can handle ends-with operator in URL
|
||||
filter = encodeURIComponent(`name:~$'2'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL ends with "2"')
|
||||
.to.equal(2);
|
||||
expect(find(operatorSelect)).to.have.value('ends-with');
|
||||
expect(find(valueInput)).to.have.value('2');
|
||||
|
||||
// can handle does-not-contain operator in URL
|
||||
filter = encodeURIComponent(`name:-~'2'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL does not contain "2"')
|
||||
.to.equal(6);
|
||||
expect(find(operatorSelect)).to.have.value('does-not-contain');
|
||||
expect(find(valueInput)).to.have.value('2');
|
||||
|
||||
// can handle escaped values in URL
|
||||
filter = encodeURIComponent(`name:~'O\\'Nolan'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL contains "O\'Nolan"')
|
||||
.to.equal(1);
|
||||
expect(find(operatorSelect)).to.have.value('contains');
|
||||
expect(find(valueInput)).to.have.value(`O'Nolan`);
|
||||
|
||||
// can handle regex special chars in URL
|
||||
filter = encodeURIComponent(`name:~'test+test'`);
|
||||
await visit('/');
|
||||
await visit(`/members?filter=${filter}`);
|
||||
await click('[data-test-button="members-filter-actions"]');
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, '# of filtered member rows - from URL contains "test+test"')
|
||||
.to.equal(0);
|
||||
expect(find(operatorSelect)).to.have.value('contains');
|
||||
expect(find(valueInput)).to.have.value(`test+test`);
|
||||
});
|
||||
|
||||
it('can filter by next billing date', async function () {
|
||||
clock = sinon.useFakeTimers({
|
||||
now: moment('2022-03-01 09:00:00.000Z').toDate(),
|
||||
|
|
|
@ -1930,36 +1930,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@miragejs/pretender-node-polyfill/-/pretender-node-polyfill-0.1.2.tgz#d26b6b7483fb70cd62189d05c95d2f67153e43f2"
|
||||
integrity sha512-M/BexG/p05C5lFfMunxo/QcgIJnMT2vDVCd00wNqK2ImZONIlEETZwWJu1QtLxtmYlSHlCFl3JNzp0tLe7OJ5g==
|
||||
|
||||
"@nexes/mongo-knex@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@nexes/mongo-knex/-/mongo-knex-0.5.0.tgz#58566614ca240bdf84a270117d72b46511b17743"
|
||||
integrity sha512-6wiTbJpy7I2xsxuvwavuwDEtJfoiaxAy4PGPFEiVziQyH3SjOFbwyqnlrKPvhNHCj2YFQHcE8rnJ3JawJVtXOA==
|
||||
dependencies:
|
||||
debug "^4.3.1"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@nexes/mongo-utils@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@nexes/mongo-utils/-/mongo-utils-0.3.1.tgz#3a1b89ec4585478dbb41277dc1fdb2689deb3b9d"
|
||||
integrity sha512-SpDr6i98GeGA2vajQtliAsUqvFbawmzC6wgaC9/+9P8R0/o+71WTzvyPNYHnXNDqy0dpxq2FX78DdN6FTSKjKA==
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@nexes/nql-lang@0.0.1", "@nexes/nql-lang@^0.0.1":
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@nexes/nql-lang/-/nql-lang-0.0.1.tgz#a13c023873f9bc11b9e4e284449c6cfbeccc8011"
|
||||
integrity sha1-oTwCOHP5vBG55OKERJxs++zMgBE=
|
||||
|
||||
"@nexes/nql@0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@nexes/nql/-/nql-0.6.0.tgz#aec2d36d0ff5300b79e950a37f8c29b195f8152b"
|
||||
integrity sha512-iI5fQPVfBAX9iM6P3S35XQhp7z7OS+7Ju7GMJGPxouBSDOkppNKh3zc4QGnrt9oMwbUN4hkZ2dsMwLs9VLmDAQ==
|
||||
dependencies:
|
||||
"@nexes/mongo-knex" "0.5.0"
|
||||
"@nexes/mongo-utils" "^0.3.1"
|
||||
"@nexes/nql-lang" "^0.0.1"
|
||||
mingo "^2.2.2"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
|
@ -2164,6 +2134,36 @@
|
|||
mobiledoc-dom-renderer "0.7.0"
|
||||
mobiledoc-text-renderer "0.4.0"
|
||||
|
||||
"@tryghost/mongo-knex@^0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/mongo-knex/-/mongo-knex-0.6.2.tgz#8eb246d9311fce6e8fcdced263c1efc8a507b3b8"
|
||||
integrity sha512-Ef1/TE74ZQaMPMy5dMmmtlqmFq3F8GtzRSvPbaNnPMN1Jn0200CQP8L5akh0r77YGtCKj5foMNsvmlTe5DqmRw==
|
||||
dependencies:
|
||||
debug "^4.3.3"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@tryghost/mongo-utils@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/mongo-utils/-/mongo-utils-0.3.3.tgz#1f35b9e9acd2762d63c72cfd097376dd8049d59c"
|
||||
integrity sha512-9Qo4jKBr8cTzgZriGQIfpq0X5bJDPDyqlLxeWHNyhIT3J8R2Mtp83zGSeuuwng33JO1cFZKMyaAQs2YTdZWeIA==
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@tryghost/nql-lang@0.3.0", "@tryghost/nql-lang@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/nql-lang/-/nql-lang-0.3.0.tgz#47e3658e46fc89222095d6cfa1927291901b2e73"
|
||||
integrity sha512-rjE0r0Fi5TCjFOL0p8wllbSb42YSgLqEXnS5AgxvhOgjMjHYkFfDX7SWN6eGFt2HeW8B15LxvG2x4eFWNoB1QA==
|
||||
|
||||
"@tryghost/nql@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/nql/-/nql-0.9.0.tgz#706d91add48043303260f92c67d7f6b8c429e982"
|
||||
integrity sha512-1b0YHY9aOI74YgA8zXA962BCYR3fofZaM1NLmoXXLwSORT9Q4yajNygMZH328NqCUiyg6KRjNnMyC/E8kLwBgQ==
|
||||
dependencies:
|
||||
"@tryghost/mongo-knex" "^0.6.2"
|
||||
"@tryghost/mongo-utils" "^0.3.3"
|
||||
"@tryghost/nql-lang" "^0.3.0"
|
||||
mingo "^2.2.2"
|
||||
|
||||
"@tryghost/string@0.1.23":
|
||||
version "0.1.23"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/string/-/string-0.1.23.tgz#7d5f556b7e99b7c5d5e4c6966626afc048b21290"
|
||||
|
@ -5794,7 +5794,7 @@ debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1, debug@~4.3.2:
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
|
|
Loading…
Add table
Reference in a new issue