Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OC-969: Add "Author Types" filter to search #731

Merged
merged 7 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions api/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ const createPublications = async (publications: Prisma.PublicationCreateInput[])
versions: {
where: {
isLatestVersion: true
},
select: {
title: true,
description: true,
keywords: true,
content: true,
currentStatus: true,
publishedDate: true,
user: {
select: {
role: true
}
}
}
}
}
Expand All @@ -31,12 +44,10 @@ const createPublications = async (publications: Prisma.PublicationCreateInput[])
id: createdPublication.id,
type: createdPublication.type,
title: latestVersion.title,
licence: latestVersion.licence,
organisationalAuthor: latestVersion.user.role === 'ORGANISATION',
description: latestVersion.description,
keywords: latestVersion.keywords,
content: latestVersion.content,
language: 'en',
currentStatus: latestVersion.currentStatus,
publishedDate: latestVersion.publishedDate,
cleanContent: convert(latestVersion.content)
}
Expand Down
32 changes: 0 additions & 32 deletions api/prisma/seeds/local/unitTesting/publications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,38 +979,6 @@ const publicationSeeds: Prisma.PublicationCreateInput[] = [
}
}
},
{
id: 'research-topic',
doi: '10.82259/cty5-2g27',
type: 'PROBLEM',
linkedTo: {
create: {
publicationToId: 'publication-problem-live',
versionToId: 'publication-problem-live-v1',
draft: false
}
},
versions: {
create: {
id: 'research-topic-v1',
versionNumber: 1,
title: 'Music',
conflictOfInterestStatus: false,
conflictOfInterestText: '',
content:
'This is an automatically-generated topic, produced in order to provide authors with a place to attach new Problem publications',
currentStatus: 'LIVE',
isLatestLiveVersion: true,
user: { connect: { id: 'octopus' } },
publicationStatus: {
create: [
{ status: 'DRAFT', createdAt: '2022-01-20T15:51:42.523Z' },
{ status: 'LIVE', createdAt: '2022-01-20T15:51:42.523Z' }
]
}
}
}
},
{
id: 'publication-peer-review-draft',
doi: '10.82259/cty5-2g28',
Expand Down
16 changes: 13 additions & 3 deletions api/scripts/reindex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ const reindex = async (): Promise<void> => {
versions: {
where: {
isLatestLiveVersion: true
},
select: {
title: true,
description: true,
keywords: true,
content: true,
publishedDate: true,
user: {
select: {
role: true
}
}
}
}
}
Expand All @@ -43,12 +55,10 @@ const reindex = async (): Promise<void> => {
id: pub.id,
type: pub.type,
title: latestLiveVersion.title,
licence: latestLiveVersion.licence,
organisationalAuthor: latestLiveVersion.user.role === 'ORGANISATION',
description: latestLiveVersion.description,
keywords: latestLiveVersion.keywords,
content: latestLiveVersion.content,
language: 'en',
currentStatus: latestLiveVersion.currentStatus,
publishedDate: latestLiveVersion.publishedDate,
cleanContent: convert(latestLiveVersion.content)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as testUtils from 'lib/testUtils';

describe('View publications + versions', () => {
beforeEach(async () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as testUtils from 'lib/testUtils';

describe('Get links from a supplied publication', () => {
beforeEach(async () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});
Expand Down
10 changes: 9 additions & 1 deletion api/src/components/publication/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ export const getOpenSearchPublications = (filters: I.OpenSearchPublicationFilter
const must: unknown[] = [];

if (filters.search) {
// @ts-ignore
must.push({
multi_match: {
query: filters.search,
Expand All @@ -373,6 +372,15 @@ export const getOpenSearchPublications = (filters: I.OpenSearchPublicationFilter
});
}

// The endpoint does accept both author types at once, but this is the same as not filtering.
if (filters.authorType && Enum.authorTypes.includes(filters.authorType)) {
must.push({
term: {
organisationalAuthor: filters.authorType === 'organisational'
}
});
}

// @ts-ignore
query.body.query.bool.must = must;

Expand Down
186 changes: 186 additions & 0 deletions api/src/components/publicationVersion/__tests__/getAll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import * as enums from 'lib/enum';
import * as testUtils from 'lib/testUtils';

describe('Get many publication versions', () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
await testUtils.openSearchSeed();
});

test('Pages have 10 results by default', async () => {
const getPublications = await testUtils.agent.get('/publication-versions');

expect(getPublications.status).toEqual(200);
expect(getPublications.body.metadata).toMatchObject({
limit: 10,
offset: 0
});
expect(getPublications.body.data.length).toEqual(10);
});

test('Can limit page size', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
limit: 5
});

expect(getPublications.status).toEqual(200);
expect(getPublications.body.metadata).toMatchObject({
limit: 5
});
expect(getPublications.body.data.length).toEqual(5);
});

test('Can get second page', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
offset: 10
});

expect(getPublications.status).toEqual(200);
expect(getPublications.body.metadata).toMatchObject({
limit: 10,
offset: 10
});
// This will change if we add more live seed publications.
expect(getPublications.body.data.length).toEqual(5);
});

test('Can order by publication date, descending', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
orderBy: 'publishedDate',
orderDirection: 'desc'
});

expect(getPublications.status).toEqual(200);
const publicationDates = getPublications.body.data.map((version) => version.publishedDate);

Check warning on line 55 in api/src/components/publicationVersion/__tests__/getAll.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Unsafe return of an `any` typed value
// Sort a copy of the dates from the results to confirm order.
const sortedPublicationDates = [...publicationDates].sort(
(a, b) => new Date(b).getTime() - new Date(a).getTime()
);
expect(publicationDates).toEqual(sortedPublicationDates);
});

test('Can order by publication date, ascending', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
orderBy: 'publishedDate',
orderDirection: 'asc'
});

expect(getPublications.status).toEqual(200);
const publicationDates = getPublications.body.data.map((version) => version.publishedDate);

Check warning on line 70 in api/src/components/publicationVersion/__tests__/getAll.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Unsafe return of an `any` typed value
// Sort a copy of the dates from the results to confirm order.
const sortedPublicationDates = [...publicationDates].sort(
(a, b) => new Date(a).getTime() - new Date(b).getTime()
);
expect(publicationDates).toEqual(sortedPublicationDates);
});

test('Invalid orderBy is rejected', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
orderBy: 'dinosaur'
});

expect(getPublications.status).toEqual(400);
expect(getPublications.body.message).toHaveLength(1);
expect(getPublications.body.message[0].keyword).toEqual('enum');
});

test('Invalid order direction is rejected', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
orderBy: 'publishedDate',
orderDirection: 'dinosaur'
});

expect(getPublications.status).toEqual(400);
expect(getPublications.body.message).toHaveLength(1);
expect(getPublications.body.message[0].keyword).toEqual('enum');
});

test('Can filter by publication type', async () => {
await Promise.all(
enums.publicationTypes.map(async (type) => {
const getPublications = await testUtils.agent.get(`/publication-versions?type=${type}`);

expect(getPublications.status).toEqual(200);
expect(getPublications.body.data.every((version) => version.publication.type === type)).toEqual(true);
})
);
});

test('Can filter by multiple publication types at once', async () => {
const getPublications = await testUtils.agent.get('/publication-versions?type=PROBLEM,PROTOCOL');

expect(getPublications.status).toEqual(200);
expect(
getPublications.body.data.every((version) => ['PROBLEM', 'PROTOCOL'].includes(version.publication.type))
).toEqual(true);
});

test('Type filtering rejects invalid types', async () => {
const getPublications = await testUtils.agent.get('/publication-versions?type=DINOSAUR');

expect(getPublications.status).toEqual(400);
expect(getPublications.body.message).toHaveLength(1);
expect(getPublications.body.message[0].keyword).toEqual('pattern');
});

test('Can filter by author type', async () => {
const getOrganisationalPublications = await testUtils.agent.get(
'/publication-versions?authorType=organisational'
);

expect(getOrganisationalPublications.status).toEqual(200);
// User role isn't returned in body so differentiate results by length (there are fewer organisational publications).
expect(getOrganisationalPublications.body.data).toHaveLength(2);

const getIndividualPublications = await testUtils.agent.get('/publication-versions?authorType=individual');

expect(getIndividualPublications.status).toEqual(200);
expect(getIndividualPublications.body.data).toHaveLength(10);
});

test('Can filter by multiple author types at once', async () => {
const getPublications = await testUtils.agent.get('/publication-versions?authorType=individual,organisational');

expect(getPublications.status).toEqual(200);
// Includes everything.
expect(getPublications.body.metadata.total).toEqual(15);
});

test('Author filtering rejects invalid types', async () => {
const getPublications = await testUtils.agent.get('/publication-versions?authorType=dinosaur');

expect(getPublications.status).toEqual(400);
expect(getPublications.body.message).toHaveLength(1);
expect(getPublications.body.message[0].keyword).toEqual('pattern');
});

test('Can filter by search term', async () => {
const getPublications = await testUtils.agent.get('/publication-versions?search=ari');

expect(getPublications.status).toEqual(200);
expect(getPublications.body.data).toHaveLength(1);
});

test('Can filter by date range', async () => {
const getPublications = await testUtils.agent.get('/publication-versions').query({
dateFrom: '2024-07-16',
dateTo: '2024-07-16'
});

expect(getPublications.status).toEqual(200);
expect(getPublications.body.data).toHaveLength(1);
});

test('Can exclude a particular publication by its ID', async () => {
const getAllPublications = await testUtils.agent.get('/publication-versions');
const allPubsCount = getAllPublications.body.metadata.total;

const getMostPublications = await testUtils.agent
.get('/publication-versions')
.query({ exclude: 'ari-publication-1' });

expect(getMostPublications.status).toEqual(200);
expect(getMostPublications.body.metadata.total).toEqual(allPubsCount - 1);
});
});
9 changes: 7 additions & 2 deletions api/src/components/publicationVersion/schema/getAll.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import * as Enum from 'enum';
import * as I from 'interface';

const getAll: I.JSONSchemaType<I.OpenSearchPublicationFilters> = {
type: 'object',
properties: {
type: {
type: 'string',
pattern:
'^((PROBLEM|PROTOCOL|ANALYSIS|REAL_WORLD_APPLICATION|HYPOTHESIS|DATA|INTERPRETATION|PEER_REVIEW)(,)?)+$',
pattern: `^((${Enum.publicationTypes.join('|')})(,)?)+$`,
default: 'PROBLEM,PROTOCOL,ANALYSIS,REAL_WORLD_APPLICATION,HYPOTHESIS,DATA,INTERPRETATION,PEER_REVIEW'
},
authorType: {
type: 'string',
pattern: `^((${Enum.authorTypes.join('|')})(,)?)+$`,
nullable: true
},
limit: {
type: 'number',
default: 10
Expand Down
2 changes: 1 addition & 1 deletion api/src/components/publicationVersion/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ export const postPublishHook = async (publicationVersion: I.PublicationVersion,
id: publicationVersion.versionOf,
type: publicationVersion.publication.type,
title: publicationVersion.title,
licence: publicationVersion.licence,
organisationalAuthor: publicationVersion.user.role === 'ORGANISATION',
description: publicationVersion.description,
keywords: publicationVersion.keywords,
content: publicationVersion.content,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as testUtils from 'lib/testUtils';

describe('get references', () => {
beforeEach(async () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});
Expand Down
2 changes: 1 addition & 1 deletion api/src/components/topic/__tests__/getTopic.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as testUtils from 'lib/testUtils';

describe('Get individual topic', () => {
beforeEach(async () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});
Expand Down
2 changes: 1 addition & 1 deletion api/src/components/topic/__tests__/getTopics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as testUtils from 'lib/testUtils';
import * as I from 'lib/interface';

describe('Get Topics', () => {
beforeEach(async () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});
Expand Down
Loading
Loading