Skip to content

Commit

Permalink
Merge pull request #732 from JiscSD/OC-972
Browse files Browse the repository at this point in the history
OC-972: Organisational vs individual author search
  • Loading branch information
finlay-jisc authored Dec 10, 2024
2 parents 41b4452 + 533bd5d commit 70e9f50
Show file tree
Hide file tree
Showing 40 changed files with 1,259 additions and 1,074 deletions.
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' });

// 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

0 comments on commit 70e9f50

Please sign in to comment.