fix(search): add support for searching by additional emails, phones, and secondary links (#17034)
## Summary - Add `additionalEmails` (EMAILS type) to tsvector search expression - Add `additionalPhones` (PHONES type) to tsvector search expression - Add `secondaryLinks` (LINKS type) to tsvector search expression This enables searching for people/companies by their secondary contact information, not just primary values. ## Test plan - [x] Unit tests for all three composite field types (16 tests passing) - [x] Integration tests for searching by secondary email, work email, partial domain - [x] Integration tests for searching by additional phone numbers - [x] Integration test for searching by secondary link URL - [x] Lint passes --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+112
-2
@@ -13,6 +13,7 @@ const nameFullNameField = {
|
||||
const jobTitleTextField = { name: 'jobTitle', type: FieldMetadataType.TEXT };
|
||||
const emailsEmailsField = { name: 'emails', type: FieldMetadataType.EMAILS };
|
||||
const phonesPhonesField = { name: 'phones', type: FieldMetadataType.PHONES };
|
||||
const linksLinksField = { name: 'domainName', type: FieldMetadataType.LINKS };
|
||||
|
||||
describe('getTsVectorColumnExpressionFromFields', () => {
|
||||
it('should generate correct expression for simple text field', () => {
|
||||
@@ -101,12 +102,121 @@ describe('getTsVectorColumnExpressionFromFields', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should properly index phone subfields', () => {
|
||||
it('should properly index phone subfields including additional phones', () => {
|
||||
const fields = [phonesPhonesField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain('phonesPrimaryPhoneNumber');
|
||||
expect(result).toContain('phonesPrimaryPhoneCallingCode');
|
||||
expect(result).not.toContain('phonesAdditionalPhones');
|
||||
|
||||
expect(result).toContain('phonesAdditionalPhones');
|
||||
expect(result).toContain(
|
||||
"COALESCE(TRANSLATE(regexp_replace(\"phonesAdditionalPhones\"::text, '\"(number|countryCode|callingCode)\"\\s*:\\s*', '', 'g'), '[]{}\",:',",
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip additional phone key names before indexing', () => {
|
||||
const fields = [phonesPhonesField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain(
|
||||
"regexp_replace(\"phonesAdditionalPhones\"::text, '\"(number|countryCode|callingCode)\"\\s*:\\s*', '', 'g')",
|
||||
);
|
||||
});
|
||||
|
||||
it('should include additional emails in search expression', () => {
|
||||
const fields = [emailsEmailsField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain('emailsPrimaryEmail');
|
||||
expect(result).toContain('emailsAdditionalEmails');
|
||||
expect(result).toContain(
|
||||
"COALESCE(public.unaccent_immutable(TRANSLATE(\"emailsAdditionalEmails\"::text, '[]\",', ' ')), '')",
|
||||
);
|
||||
expect(result).toContain(
|
||||
"COALESCE(public.unaccent_immutable(TRANSLATE(REPLACE(\"emailsAdditionalEmails\"::text, '@', ' '), '[]\",', ' ')), '')",
|
||||
);
|
||||
});
|
||||
|
||||
it('should include secondary links in search expression for LINKS type', () => {
|
||||
const fields = [linksLinksField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain('domainNamePrimaryLinkLabel');
|
||||
expect(result).toContain('domainNamePrimaryLinkUrl');
|
||||
expect(result).toContain('domainNameSecondaryLinks');
|
||||
expect(result).toContain(
|
||||
"COALESCE(public.unaccent_immutable(TRANSLATE(regexp_replace(\"domainNameSecondaryLinks\"::text, '\"(label|url)\"\\s*:\\s*', '', 'g'), '[]{}\",:',",
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip secondary link key names before indexing', () => {
|
||||
const fields = [linksLinksField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain(
|
||||
"regexp_replace(\"domainNameSecondaryLinks\"::text, '\"(label|url)\"\\s*:\\s*', '', 'g')",
|
||||
);
|
||||
});
|
||||
|
||||
describe('NULL/empty JSON column handling', () => {
|
||||
it('should wrap additionalEmails JSON column with COALESCE for NULL safety', () => {
|
||||
const fields = [emailsEmailsField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain(
|
||||
'COALESCE(public.unaccent_immutable(TRANSLATE("emailsAdditionalEmails"::text',
|
||||
);
|
||||
expect(result).toMatch(
|
||||
/COALESCE\(public\.unaccent_immutable\(TRANSLATE\("emailsAdditionalEmails"::text.*\), ''\)/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should wrap additionalPhones JSON column with COALESCE for NULL safety', () => {
|
||||
const fields = [phonesPhonesField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain(
|
||||
'COALESCE(TRANSLATE(regexp_replace("phonesAdditionalPhones"::text',
|
||||
);
|
||||
expect(result).toMatch(
|
||||
/COALESCE\(TRANSLATE\(regexp_replace\("phonesAdditionalPhones"::text.*\), ''\)/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should wrap secondaryLinks JSON column with COALESCE for NULL safety', () => {
|
||||
const fields = [linksLinksField] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
expect(result).toContain(
|
||||
'COALESCE(public.unaccent_immutable(TRANSLATE(regexp_replace("domainNameSecondaryLinks"::text',
|
||||
);
|
||||
expect(result).toMatch(
|
||||
/COALESCE\(public\.unaccent_immutable\(TRANSLATE\(regexp_replace\("domainNameSecondaryLinks"::text.*\), ''\)/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should use empty string fallback for all JSON array columns', () => {
|
||||
const fields = [
|
||||
emailsEmailsField,
|
||||
phonesPhonesField,
|
||||
linksLinksField,
|
||||
] as FieldTypeAndNameMetadata[];
|
||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||
|
||||
const additionalEmailsCoalesce = result.includes(
|
||||
"COALESCE(public.unaccent_immutable(TRANSLATE(\"emailsAdditionalEmails\"::text, '[]\",', ' ')), '')",
|
||||
);
|
||||
const additionalPhonesCoalesce = result.includes(
|
||||
"COALESCE(TRANSLATE(regexp_replace(\"phonesAdditionalPhones\"::text, '\"(number|countryCode|callingCode)\"\\s*:\\s*', '', 'g'), '[]{}\",:',",
|
||||
);
|
||||
const secondaryLinksCoalesce = result.includes(
|
||||
"COALESCE(public.unaccent_immutable(TRANSLATE(regexp_replace(\"domainNameSecondaryLinks\"::text, '\"(label|url)\"\\s*:\\s*', '', 'g'), '[]{}\",:',",
|
||||
);
|
||||
|
||||
expect(additionalEmailsCoalesce).toBe(true);
|
||||
expect(additionalPhonesCoalesce).toBe(true);
|
||||
expect(secondaryLinksCoalesce).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+25
-1
@@ -35,6 +35,7 @@ export const getTsVectorColumnExpressionFromFields = (
|
||||
);
|
||||
const concatenatedExpression = columnExpressions.join(" || ' ' || ");
|
||||
|
||||
// Note: changing this expression requires reindexing/backfilling existing searchVector values.
|
||||
return `to_tsvector('simple', ${concatenatedExpression})`;
|
||||
};
|
||||
|
||||
@@ -68,6 +69,7 @@ const getColumnExpressionsFromField = (
|
||||
if (fieldMetadataTypeAndName.type === FieldMetadataType.PHONES) {
|
||||
const phoneNumberColumn = `"${fieldMetadataTypeAndName.name}PrimaryPhoneNumber"`;
|
||||
const callingCodeColumn = `"${fieldMetadataTypeAndName.name}PrimaryPhoneCallingCode"`;
|
||||
const additionalPhonesColumn = `"${fieldMetadataTypeAndName.name}AdditionalPhones"`;
|
||||
|
||||
const internationalFormats = [
|
||||
`COALESCE(${callingCodeColumn} || ${phoneNumberColumn}, '')`,
|
||||
@@ -75,7 +77,29 @@ const getColumnExpressionsFromField = (
|
||||
`COALESCE('0' || ${phoneNumberColumn}, '')`,
|
||||
];
|
||||
|
||||
return [...baseExpressions, ...internationalFormats];
|
||||
const additionalPhonesExpression = `COALESCE(TRANSLATE(regexp_replace(${additionalPhonesColumn}::text, '"(number|countryCode|callingCode)"\\s*:\\s*', '', 'g'), '[]{}",:', ' '), '')`;
|
||||
|
||||
return [
|
||||
...baseExpressions,
|
||||
...internationalFormats,
|
||||
additionalPhonesExpression,
|
||||
];
|
||||
}
|
||||
|
||||
if (fieldMetadataTypeAndName.type === FieldMetadataType.LINKS) {
|
||||
const secondaryLinksColumn = `"${fieldMetadataTypeAndName.name}SecondaryLinks"`;
|
||||
|
||||
const secondaryLinksExpression = `COALESCE(public.unaccent_immutable(TRANSLATE(regexp_replace(${secondaryLinksColumn}::text, '"(label|url)"\\s*:\\s*', '', 'g'), '[]{}",:', ' ')), '')`;
|
||||
|
||||
return [...baseExpressions, secondaryLinksExpression];
|
||||
}
|
||||
|
||||
if (fieldMetadataTypeAndName.type === FieldMetadataType.EMAILS) {
|
||||
const additionalEmailsColumn = `"${fieldMetadataTypeAndName.name}AdditionalEmails"`;
|
||||
|
||||
const additionalEmailsExpression = `COALESCE(public.unaccent_immutable(TRANSLATE(${additionalEmailsColumn}::text, '[]",', ' ')), '') || ' ' || COALESCE(public.unaccent_immutable(TRANSLATE(REPLACE(${additionalEmailsColumn}::text, '@', ' '), '[]",', ' ')), '')`;
|
||||
|
||||
return [...baseExpressions, additionalEmailsExpression];
|
||||
}
|
||||
|
||||
return baseExpressions;
|
||||
|
||||
@@ -5,5 +5,7 @@ export const TEST_PERSON_4_ID = '777a8457-eb2d-40ac-a707-551b615b6983';
|
||||
export const TEST_PERSON_5_ID = '777a8457-eb2d-40ac-a707-551b615b6984';
|
||||
export const TEST_PERSON_6_ID = '777a8457-eb2d-40ac-a707-551b615b6985';
|
||||
export const TEST_PERSON_7_ID = '777a8457-eb2d-40ac-a707-551b615b6986';
|
||||
export const TEST_PERSON_8_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
|
||||
export const TEST_PERSON_9_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
|
||||
export const NOT_EXISTING_TEST_PERSON_ID =
|
||||
'777a8457-eb2d-40ac-a707-551b615b6990';
|
||||
|
||||
+275
-4
@@ -13,6 +13,8 @@ import {
|
||||
TEST_PERSON_5_ID,
|
||||
TEST_PERSON_6_ID,
|
||||
TEST_PERSON_7_ID,
|
||||
TEST_PERSON_8_ID,
|
||||
TEST_PERSON_9_ID,
|
||||
} from 'test/integration/constants/test-person-ids.constants';
|
||||
import {
|
||||
TEST_PET_ID_1,
|
||||
@@ -54,6 +56,7 @@ describe('SearchResolver', () => {
|
||||
primaryPhoneNumber: '5551234567',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [],
|
||||
},
|
||||
},
|
||||
{ id: TEST_PERSON_3_ID, name: { firstName: 'searchInput3' } },
|
||||
@@ -86,11 +89,55 @@ describe('SearchResolver', () => {
|
||||
jobTitle: 'Manager',
|
||||
emails: { primaryEmail: 'francois@naive.com' },
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_8_ID,
|
||||
name: { firstName: 'MultiEmail', lastName: 'Person' },
|
||||
emails: {
|
||||
primaryEmail: 'primary@example.com',
|
||||
additionalEmails: ['secondary@example.com', 'work@company.org'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: TEST_PERSON_9_ID,
|
||||
name: { firstName: 'MultiPhone', lastName: 'Person' },
|
||||
emails: {
|
||||
primaryEmail: 'empty@arrays.com',
|
||||
additionalEmails: [],
|
||||
},
|
||||
phones: {
|
||||
primaryPhoneNumber: '9998887777',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
additionalPhones: [
|
||||
{ number: '1112223333', countryCode: 'US', callingCode: '+1' },
|
||||
{ number: '4445556666', countryCode: 'GB', callingCode: '+44' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const companies = [
|
||||
{ id: TEST_COMPANY_1_ID, name: 'Café Corp' },
|
||||
{ id: TEST_COMPANY_2_ID, name: 'Naïve Solutions' },
|
||||
{
|
||||
id: TEST_COMPANY_1_ID,
|
||||
name: 'Café Corp',
|
||||
domainName: {
|
||||
primaryLinkLabel: 'Main Site',
|
||||
primaryLinkUrl: 'https://links.example.com',
|
||||
secondaryLinks: [
|
||||
{ label: 'DocsPortal', url: 'docs.links.example.com' },
|
||||
{ label: 'SupportHub', url: 'support.links.example.com' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: TEST_COMPANY_2_ID,
|
||||
name: 'Naïve Solutions',
|
||||
domainName: {
|
||||
primaryLinkLabel: 'NaivePortal',
|
||||
primaryLinkUrl: 'https://naive.portal.example',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const pets = [
|
||||
@@ -108,6 +155,8 @@ describe('SearchResolver', () => {
|
||||
francoisPerson,
|
||||
josePersonNoAccent,
|
||||
francoisPersonNoAccent,
|
||||
multiEmailPerson,
|
||||
multiPhonePerson,
|
||||
] = persons;
|
||||
const [cafeCorp, naiveCorp] = companies;
|
||||
const [searchInput1Pet, searchInput2Pet, cafePet, naivePet] = pets;
|
||||
@@ -177,6 +226,8 @@ describe('SearchResolver', () => {
|
||||
francoisPerson.id,
|
||||
josePersonNoAccent.id,
|
||||
francoisPersonNoAccent.id,
|
||||
multiEmailPerson.id,
|
||||
multiPhonePerson.id,
|
||||
naiveCorp.id,
|
||||
cafeCorp.id,
|
||||
searchInput1Pet.id,
|
||||
@@ -189,7 +240,7 @@ describe('SearchResolver', () => {
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0, tsRankCD: 0 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: francoisPersonNoAccent.id,
|
||||
person: multiPhonePerson.id,
|
||||
company: cafeCorp.id,
|
||||
pet: naivePet.id,
|
||||
},
|
||||
@@ -639,9 +690,9 @@ describe('SearchResolver', () => {
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [
|
||||
naiveCorp.id,
|
||||
francoisPerson.id,
|
||||
francoisPersonNoAccent.id,
|
||||
naiveCorp.id,
|
||||
naivePet.id,
|
||||
],
|
||||
pageInfo: {
|
||||
@@ -905,6 +956,226 @@ describe('SearchResolver', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find person by additional email (secondary email)',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'secondary@example.com',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiEmailPerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiEmailPerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find person by additional email (work email)',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'work@company.org',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiEmailPerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiEmailPerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find person by partial additional email',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'company.org',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiEmailPerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiEmailPerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find person by additional phone number',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: '1112223333',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiPhonePerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiPhonePerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find person by second additional phone number',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: '4445556666',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiPhonePerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiPhonePerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find company by secondary link url',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'docs.links.example.com',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [cafeCorp.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
company: cafeCorp.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should find company by secondary link label',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'DocsPortal',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [cafeCorp.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
company: cafeCorp.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should handle empty additional emails array',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'empty@arrays.com',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [multiPhonePerson.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: multiPhonePerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should handle empty additional phones array',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: '5551234567',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [searchInput2Person.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
person: searchInput2Person.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should handle empty secondary links array',
|
||||
context: {
|
||||
input: {
|
||||
searchInput: 'NaivePortal',
|
||||
excludedObjectNameSingulars: ['workspaceMember'],
|
||||
limit: 50,
|
||||
},
|
||||
eval: {
|
||||
orderedRecordIds: [naiveCorp.id],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
decodedEndCursor: {
|
||||
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
|
||||
lastRecordIdsPerObject: {
|
||||
company: naiveCorp.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it.each(eachTestingContextFilter(testsUseCases))(
|
||||
|
||||
Reference in New Issue
Block a user