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-972: Organisational vs individual author search #732

Merged
merged 19 commits into from
Dec 10, 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
4 changes: 2 additions & 2 deletions api/prisma/seeds/local/dev/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const userSeeds: Prisma.UserCreateInput[] = [
{
id: 'octopus',
orcid: 'XXXX-XXXX-XXXX-XXXX',
firstName: 'Science',
lastName: 'Octopus',
firstName: 'Octopus',
role: 'ORGANISATION',
email: 'octopus@jisc.ac.uk',
apiKey: 'kjahskjhuhaushkjhaskjhjkahsd'
},
Expand Down
4 changes: 2 additions & 2 deletions api/prisma/seeds/local/unitTesting/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const userSeeds: Prisma.UserCreateInput[] = [
{
id: 'octopus',
orcid: 'XXXX-XXXX-XXXX-XXXX',
firstName: 'Science',
lastName: 'Octopus',
firstName: 'Octopus',
role: 'ORGANISATION',
email: 'octopus@jisc.ac.uk',
apiKey: 'kjahskjhuhaushkjhaskjhjkahsd'
},
Expand Down
45 changes: 36 additions & 9 deletions api/src/components/user/__tests__/getUsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,50 @@ describe('Get Users', () => {
expect(response.body.metadata.total).toEqual(12);
});

test('Get "Science Octopus" user by searching for "Science"', async () => {
const response = await testUtils.agent.get('/users?search=Science');
test('Get "Octopus" user by searching for "Octopus"', async () => {
const response = await testUtils.agent.get('/users?search=Octopus');
expect(response.status).toEqual(200);
expect(response.body.data.length).toEqual(1);
const user = response.body.data[0];
expect(`${user.firstName} ${user.lastName}`).toEqual('Science Octopus');
expect(response.body.data).toMatchObject([
{
id: 'octopus',
firstName: 'Octopus'
}
]);
});

test('Partial matches work on firstName/lastName', async () => {
const testResponse1 = await testUtils.agent.get('/users?search=Sci');
const testResponse1 = await testUtils.agent.get('/users?search=Octo');
expect(testResponse1.status).toEqual(200);
expect(testResponse1.body.data.length).toEqual(1);
expect(testResponse1.body.metadata.total).toEqual(1);

const testResponse2 = await testUtils.agent.get('/users?search=Octop');
const testResponse2 = await testUtils.agent.get('/users?search=Coauth');
expect(testResponse2.status).toEqual(200);
expect(testResponse2.body.data.length).toEqual(1);
expect(testResponse2.body.metadata.total).toEqual(1);
expect(testResponse2.body.data.length).toEqual(6);
expect(testResponse2.body.metadata.total).toEqual(6);
});

test('Can filter by role', async () => {
const response = await testUtils.agent.get('/users?role=USER');
expect(response.status).toEqual(200);
expect(response.body.data.length).toEqual(9);
expect(response.body.metadata.total).toEqual(9);
});

test('Filtering by ORGANISATION role excludes accounts without any live publication versions', async () => {
const response = await testUtils.agent.get('/users?role=ORGANISATION');
expect(response.status).toEqual(200);
expect(response.body.data.some((user) => user.id === 'test-organisational-account-2')).toBe(false);
});

test('Can filter by role and name query at once', async () => {
const response = await testUtils.agent.get('/users?role=ORGANISATION&search=department');
expect(response.status).toEqual(200);
expect(response.body.data).toMatchObject([
{
id: 'test-organisational-account-1',
firstName: 'Test ARI Department (UK)'
}
]);
});
});
4 changes: 4 additions & 0 deletions api/src/components/user/schema/getAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const getAllSchema: I.Schema = {
enum: ['asc', 'desc'],
default: 'desc'
},
role: {
type: 'string',
enum: ['USER', 'ORGANISATION']
},
search: {
type: 'string',
default: ''
Expand Down
53 changes: 43 additions & 10 deletions api/src/components/user/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,27 @@ export const updateEmail = (orcid: string, email: string) =>
});

export const getAll = async (filters: I.UserFilters) => {
let where: Prisma.UserWhereInput = {};
const prismaFilters: Prisma.UserWhereInput[] = [];

if (filters.search) {
const searchQuery = Helpers.sanitizeSearchQuery(filters.search);
where = {
firstName: {
search: searchQuery + ':*'
},
lastName: {
search: searchQuery + ':*'
}
};
prismaFilters.push({
OR: [
{
firstName: {
search: searchQuery + ':*'
}
},
{
lastName: {
search: searchQuery + ':*'
}
}
]
});
} else {
where = {
// Exclude unnamed users.
prismaFilters.push({
OR: [
{
firstName: {
Expand All @@ -75,9 +82,35 @@ export const getAll = async (filters: I.UserFilters) => {
}
}
]
});
}

if (filters.role) {
const basicRoleFilter = {
role: filters.role
};
prismaFilters.push(
filters.role === 'ORGANISATION'
? {
AND: [
basicRoleFilter,
{
publicationVersions: {
some: {
currentStatus: 'LIVE'
}
}
}
]
}
: basicRoleFilter
);
}

const where = {
AND: prismaFilters
};

const users = await client.prisma.user.findMany({
take: Number(filters.limit) || 10,
skip: Number(filters.offset) || 0,
Expand Down
1 change: 1 addition & 0 deletions api/src/lib/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export interface User {

export interface UserFilters {
search?: string;
role?: Role;
limit?: string;
offset?: string;
orderBy?: UserOrderBy;
Expand Down
5 changes: 1 addition & 4 deletions e2e/tests/LoggedIn/browse.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ test.describe('Browse', () => {
await page.locator(PageModel.header.browseButton).click();

// Check links
await expect(page.locator(PageModel.browse.viewAllPublications)).toHaveAttribute(
'href',
'/search/publications?type=PROBLEM,HYPOTHESIS,PROTOCOL,DATA,ANALYSIS,INTERPRETATION,REAL_WORLD_APPLICATION,PEER_REVIEW'
);
await expect(page.locator(PageModel.browse.viewAllPublications)).toHaveAttribute('href', '/search');
await expect(page.locator(PageModel.browse.viewAllAuthors)).toHaveAttribute('href', '/search/authors');

// Expect 5 cards
Expand Down
14 changes: 7 additions & 7 deletions e2e/tests/LoggedIn/livePublication.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,24 +104,24 @@ test.describe('Live Publication', () => {

test('Author profile', async ({ browser }) => {
const page = await Helpers.users.getPageAsUser(browser);
await page.goto(`/publications/cl3fz14dr0001es6i5ji51rq4`, { waitUntil: 'domcontentloaded' });
await page.goto(`/publications/publication-user-6-hypothesis-1-live`, { waitUntil: 'domcontentloaded' });
finlay-jisc marked this conversation as resolved.
Show resolved Hide resolved

// Check and click author link
await page.locator(PageModel.livePublication.authorLink).click();
await page.waitForURL(`/authors/octopus`);
await page.getByRole('link', { name: 'G. Murphy' }).click();
await page.waitForURL(`/authors/test-user-6-grace-murphy`);

// Check name
await expect(page.locator(PageModel.authorInfo.name)).toBeVisible();

// Check ORCID data sections
for await (const orcidDataSection of PageModel.authorInfo.orcidData) {
for await (const orcidDataSection of PageModel.profilePage.orcidDataSections) {
await expect(page.locator(orcidDataSection)).toBeVisible();
}

// Check Author publications section
await page.locator(PageModel.authorInfo.showAll).click();
await page.waitForSelector(PageModel.authorInfo.result);
await expect(page.locator(PageModel.authorInfo.result)).toBeVisible();
await page.locator(PageModel.profilePage.showAll).click();
await page.waitForSelector(PageModel.profilePage.result);
await expect(page.locator(PageModel.profilePage.result)).toBeVisible();
});

test('Download pdf/json', async ({ browser, headless }) => {
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/LoggedIn/profile.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test.describe('Live author page', () => {
expect(page.locator(PageModel.profilePage.works)).toBeVisible();

// check Octopus publications section
await expect(page.locator(PageModel.profilePage.octopusPublications)).toBeVisible();
await expect(page.locator(PageModel.organisationalUserInfo.octopusPublications)).toBeVisible();
});

test('ORCID profile link opens in a new tab', async () => {
Expand Down
5 changes: 1 addition & 4 deletions e2e/tests/LoggedOut/browse.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ test.describe('Browse', () => {
await page.locator(PageModel.header.browseButton).click();

// Check links
await expect(page.locator(PageModel.browse.viewAllPublications)).toHaveAttribute(
'href',
'/search/publications?type=PROBLEM,HYPOTHESIS,PROTOCOL,DATA,ANALYSIS,INTERPRETATION,REAL_WORLD_APPLICATION,PEER_REVIEW'
);
await expect(page.locator(PageModel.browse.viewAllPublications)).toHaveAttribute('href', '/search');
await expect(page.locator(PageModel.browse.viewAllAuthors)).toHaveAttribute('href', '/search/authors');

// Expect 5 cards
Expand Down
19 changes: 9 additions & 10 deletions e2e/tests/LoggedOut/profile.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import * as Helpers from '../helpers';
import { expect, Page, test } from '@playwright/test';
import { PageModel } from '../PageModel';

test.describe('Science Octopus profile', () => {
test.describe('Octopus profile', () => {
let page: Page;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
// navigate to Science Octopus profile page
// navigate to Octopus profile page
await page.goto(`/authors/octopus`, {
waitUntil: 'domcontentloaded'
});
await expect(page).toHaveTitle('Author: XXXX-XXXX-XXXX-XXXX - Octopus | Built for Researchers');
await expect(page).toHaveTitle('Author: Octopus - Octopus | Built for Researchers');
});

test.afterAll(async () => {
await page.close();
});

test('Octopus publications pagination', async () => {
// check user full name
await expect(page.locator('h1')).toHaveText('Science Octopus');
// check user name
await expect(page.locator('h1')).toHaveText('Octopus');

// check Octopus publications section
const octopusPublicationsHeader = page.locator(PageModel.profilePage.octopusPublications);
const octopusPublicationsHeader = page.locator(PageModel.organisationalUserInfo.octopusPublications);

await expect(octopusPublicationsHeader).toBeVisible();

Expand All @@ -32,7 +31,7 @@ test.describe('Science Octopus profile', () => {
const octopusPublicationsSection = octopusPublicationsHeader.locator('xpath=..');

// initially, only 10 publications should be visible
expect(await octopusPublicationsSection.locator('a[role=button]').count()).toEqual(10);
expect(await octopusPublicationsSection.locator('a').count()).toEqual(10);

// press "Show More" button to see more publications
await expect(page.locator("'Show More'")).toBeVisible();
Expand All @@ -47,7 +46,7 @@ test.describe('Science Octopus profile', () => {
await page.waitForTimeout(500);

// the next 10 pubs should be loaded
expect(await octopusPublicationsSection.locator('a[role=button]').count()).toEqual(20);
expect(await octopusPublicationsSection.locator('a').count()).toEqual(20);

// press "Show More" button again
await Promise.all([
Expand All @@ -61,6 +60,6 @@ test.describe('Science Octopus profile', () => {
await page.waitForTimeout(500);

// 30 publications should now be visible in the UI
expect(await octopusPublicationsSection.locator('a[role=button]').count()).toEqual(30);
expect(await octopusPublicationsSection.locator('a').count()).toEqual(30);
});
});
21 changes: 12 additions & 9 deletions e2e/tests/PageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const PageModel = {
'h2:has-text("Conflict of interest")'
],
doiLink: 'aside [aria-label="DOI link: https://handle.test.datacite.org/10.82259/cl3fz14dr0001es6i5ji51rq4"]',
authorLink: 'a[href="/authors/octopus"]:has-text("S. Octopus")',
authorLink: 'a[href="/authors/octopus"]:has-text("Octopus")',
signInForMoreButton: 'text=Sign in for more actions',
verifyEmailForMoreButton: 'text=Verify your email for more actions',
addBookmark: '[title="Bookmark this publication"]',
Expand All @@ -108,22 +108,25 @@ export const PageModel = {
resolveFlag: 'text=Resolve flag',
confirmResolve: 'button[aria-label="Resolve"]'
},
authorInfo: {
name: 'text=Science Octopus',
organisationalUserInfo: {
name: 'text=Octopus',
orcid: 'a:has-text("XXXX-XXXX-XXXX-XXXX")',
orcidData: [
octopusPublications: 'h2:has-text("Octopus publications")'
},
authorInfo: {
name: 'text=Grace Murphy'
},
profilePage: {
orcidDataSections: [
'section > h2:has-text("Employment")',
'section > h2:has-text("Education")',
'section > h2:has-text("Works")'
],
showAll: 'button:has-text("Show More")',
result: '_react=SearchResult[publicationVersion.publication.id="cl3fz14dr0001es6i5ji51rq4"]'
},
profilePage: {
employment: 'h2:has-text("Employment")',
education: 'h2:has-text("Education")',
works: 'h2:has-text("Works")',
octopusPublications: 'h2:has-text("Octopus publications")'
showAll: 'button:has-text("Show More")',
result: '_react=SearchResult[publicationVersion.publication.id="publication-user-6-hypothesis-1-live"]'
},
login: {
username: '#username-input',
Expand Down
15 changes: 15 additions & 0 deletions ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ $ ~/ui$ npm i
$ ~/ui$ npm run dev
```

### Warning about self-signed certificate generation

Note: because of the `--experimental-https` flag, the process may prompt for your password in order to generate a self-signed certificate. Due to the use of concurrently to run multiple processes under one command, this can get a bit lost or unclear in the output, e.g.:

```
[next] ⚠ Self-signed certificates are currently an experimental feature, use with caution.
[next] Downloading mkcert package...
[next] Download response was successful, writing to disk
[next] Attempting to generate self signed certificate. This may prompt for your password
Sudo password:[types]
[types] 12:02:13 PM - Found 0 errors. Watching for file changes.
```

From this output, you are free to type the password; the certificate will be generated and the command will proceed correctly.

Open [https://localhost:3001](https://localhost:3001) with your browser to see the result.

To view any dynamic pages, you will also need to start the API. More information can be found in the [API readme](../api/README.md).
Expand Down
Loading
Loading