Skip to content

Commit

Permalink
Merge pull request #1140 from cardstack/cs-6667-search-by-coderef-ie-…
Browse files Browse the repository at this point in the history
…catalog-entries

Add support for `formatValue` hook to serialize query values
  • Loading branch information
habdelra authored Apr 5, 2024
2 parents 71d03aa + 75f1aa1 commit 94abfaa
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 67 deletions.
16 changes: 12 additions & 4 deletions packages/base/boolean.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
primitive,
serialize,
queryableValue,
formatQuery,
Component,
useIndexBasedKey,
FieldDef,
Expand Down Expand Up @@ -48,10 +49,10 @@ export default class BooleanField extends FieldDef {
}

static [queryableValue](val: any): boolean {
if (typeof val === 'string') {
return val.toLowerCase() === 'true';
}
return Boolean(val);
return asBoolean(val);
}
static [formatQuery](val: any): boolean {
return asBoolean(val);
}

static embedded = View;
Expand Down Expand Up @@ -87,3 +88,10 @@ export default class BooleanField extends FieldDef {
}
};
}

function asBoolean(val: any): boolean {
if (typeof val === 'string') {
return val.toLowerCase() === 'true';
}
return Boolean(val);
}
15 changes: 15 additions & 0 deletions packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const useIndexBasedKey = Symbol.for('cardstack-use-index-based-key');
export const fieldDecorator = Symbol.for('cardstack-field-decorator');
export const fieldType = Symbol.for('cardstack-field-type');
export const queryableValue = Symbol.for('cardstack-queryable-value');
export const formatQuery = Symbol.for('cardstack-format-query');
export const relativeTo = Symbol.for('cardstack-relative-to');
export const realmInfo = Symbol.for('cardstack-realm-info');
export const realmURL = Symbol.for('cardstack-realm-url');
Expand Down Expand Up @@ -1606,6 +1607,13 @@ export class BaseDef {
}
}

static [formatQuery](value: any): any {
if (primitive in this) {
return value;
}
throw new Error(`Cannot format query value for composite card/field`);
}

static [queryableValue](value: any, stack: BaseDef[] = []): any {
if (primitive in this) {
return value;
Expand Down Expand Up @@ -2140,6 +2148,13 @@ export function getQueryableValue(
return fieldOrCard.queryableValue(value, stack);
}

export function formatQueryValue(
field: Field<typeof BaseDef>,
queryValue: any,
): any {
return field.card[formatQuery](queryValue);
}

function peekAtField(instance: BaseDef, fieldName: string): any {
let field = getField(
Reflect.getPrototypeOf(instance)!.constructor as typeof BaseDef,
Expand Down
29 changes: 20 additions & 9 deletions packages/base/code-ref.gts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
primitive,
serialize,
deserialize,
formatQuery,
queryableValue,
CardDef,
BaseDefConstructor,
Expand Down Expand Up @@ -51,18 +52,28 @@ export default class CodeRefField extends FieldDef {
codeRef: ResolvedCodeRef | undefined,
stack: CardDef[] = [],
) {
if (codeRef) {
// if a stack is passed in, use the containing card to resolve relative references
let moduleHref =
stack.length > 0
? new URL(codeRef.module, stack[0][relativeTo]).href
: codeRef.module;
return `${moduleHref}/${codeRef.name}`;
}
return undefined;
return maybeSerializeCodeRef(codeRef, stack);
}
static [formatQuery](codeRef: ResolvedCodeRef) {
return maybeSerializeCodeRef(codeRef);
}

static embedded = class Embedded extends BaseView {};
// The edit template is meant to be read-only, this field card is not mutable
static edit = class Edit extends BaseView {};
}

function maybeSerializeCodeRef(
codeRef: ResolvedCodeRef | undefined,
stack: CardDef[] = [],
) {
if (codeRef) {
// if a stack is passed in, use the containing card to resolve relative references
let moduleHref =
stack.length > 0
? new URL(codeRef.module, stack[0][relativeTo]).href
: codeRef.module;
return `${moduleHref}/${codeRef.name}`;
}
return undefined;
}
9 changes: 9 additions & 0 deletions packages/host/tests/cards/event.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { contains, field, CardDef } from 'https://cardstack.com/base/card-api';
import DateField from 'https://cardstack.com/base/date';
import StringField from 'https://cardstack.com/base/string';

export class Event extends CardDef {
@field title = contains(StringField);
@field venue = contains(StringField);
@field date = contains(DateField);
}
68 changes: 38 additions & 30 deletions packages/host/tests/cards/silly-number.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Component,
FieldDef,
queryableValue,
formatQuery,
primitive,
} from 'https://cardstack.com/base/card-api';

Expand All @@ -19,40 +20,47 @@ class View extends Component<typeof SillyNumberField> {

export default class SillyNumberField extends FieldDef {
static [primitive]: string[];
static [formatQuery](value: string[]) {
return format(value);
}
static [queryableValue](value: string[] | undefined) {
if (!value) {
return undefined;
}
let result = value.map((word) => {
switch (word) {
case 'zero':
return '0';
case 'one':
return '1';
case 'two':
return '2';
case 'three':
return '3';
case 'four':
return '4';
case 'five':
return '5';
case 'six':
return '6';
case 'seven':
return '7';
case 'eight':
return '8';
case 'nine':
return '9';
default:
return '0';
}
});
return parseInt(result.join(''));
return format(value);
}

static embedded = View;
static isolated = View;
static edit = View;
}

function format(value: string[] | undefined) {
if (!value) {
return undefined;
}
let result = value.map((word) => {
switch (word) {
case 'zero':
return '0';
case 'one':
return '1';
case 'two':
return '2';
case 'three':
return '3';
case 'four':
return '4';
case 'five':
return '5';
case 'six':
return '6';
case 'seven':
return '7';
case 'eight':
return '8';
case 'nine':
return '9';
default:
return '0';
}
});
return parseInt(result.join(''));
}
51 changes: 50 additions & 1 deletion packages/host/tests/integration/search-index-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2953,6 +2953,38 @@ posts/ignore-me.json
},
},
},
'event-1.json': {
data: {
type: 'card',
attributes: {
title: "Mango's Birthday",
venue: 'Dog Park',
date: '2024-10-30',
},
meta: {
adoptsFrom: {
module: `${testModuleRealm}event`,
name: 'Event',
},
},
},
},
'event-2.json': {
data: {
type: 'card',
attributes: {
title: "Van Gogh's Birthday",
venue: 'Backyard',
date: '2024-11-19',
},
meta: {
adoptsFrom: {
module: `${testModuleRealm}event`,
name: 'Event',
},
},
},
},
'mango.json': {
data: {
type: 'card',
Expand Down Expand Up @@ -3348,7 +3380,7 @@ posts/ignore-me.json
}
});

test('can use a range filter with custom queryableValue', async function (assert) {
test('can use a range filter with custom formatQuery', async function (assert) {
let { data: matching } = await indexer.search({
filter: {
on: { module: `${testModuleRealm}dog`, name: 'Dog' },
Expand All @@ -3363,6 +3395,21 @@ posts/ignore-me.json
);
});

test('can use an eq filter with a date field', async function (assert) {
let { data: matching } = await indexer.search({
filter: {
on: { module: `${testModuleRealm}event`, name: 'Event' },
eq: {
date: '2024-10-30',
},
},
});
assert.deepEqual(
matching.map((m) => m.id),
[`${paths.url}event-1`],
);
});

test(`gives a good error when query refers to missing card`, async function (assert) {
try {
await indexer.search({
Expand Down Expand Up @@ -3663,6 +3710,8 @@ posts/ignore-me.json
`${paths.url}mango`, // dog
`${paths.url}ringo`, // dog
`${paths.url}vangogh`, // dog
`${paths.url}event-1`, // event
`${paths.url}event-2`, // event
`${paths.url}friend2`, // friend
`${paths.url}friend1`, // friend
`${paths.url}person-card1`, // person
Expand Down
Loading

0 comments on commit 94abfaa

Please sign in to comment.