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-817: Display red flags on public profile page #751

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
154 changes: 154 additions & 0 deletions api/prisma/seeds/local/dev/otherPublications.ts

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions api/prisma/seeds/local/unitTesting/publications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,19 @@ const publicationSeeds: Prisma.PublicationCreateInput[] = [
]
},
publicationFlags: {
create: {
id: 'publication-problem-live-flag',
createdBy: 'test-user-2',
category: 'PLAGIARISM',
flagComments: {
create: {
createdBy: 'test-user-2',
comment: 'This is a comment'
create: [
{
id: 'publication-problem-live-flag-1',
createdBy: 'test-user-2',
category: 'PLAGIARISM',
flagComments: {
create: {
createdBy: 'test-user-2',
comment: 'This is a comment'
}
}
}
}
]
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion api/serverless-config-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ functions:
handler: dist/src/components/flag/routes.getUserFlags
events:
- http:
path: ${self:custom.versions.v1}/user/{userId}/flags
path: ${self:custom.versions.v1}/users/{userId}/flags
method: GET
cors: true
# Images
Expand Down
8 changes: 4 additions & 4 deletions api/src/components/flag/__tests__/createFlagComment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Create flags comments on a flag', () => {

test('User who created the flag can leave comments', async () => {
const createFlagComment = await testUtils.agent
.post('/flags/publication-problem-live-flag/comment')
.post('/flags/publication-problem-live-flag-1/comment')
.query({
apiKey: '987654321'
})
Expand All @@ -21,7 +21,7 @@ describe('Create flags comments on a flag', () => {

test('Owner of the publication can leave comments', async () => {
const createFlagComment = await testUtils.agent
.post('/flags/publication-problem-live-flag/comment')
.post('/flags/publication-problem-live-flag-1/comment')
.query({
apiKey: '123456789'
})
Expand Down Expand Up @@ -59,7 +59,7 @@ describe('Create flags comments on a flag', () => {

test('You can only leave a comment if you are either the author of the publication or the flagger', async () => {
const createFlagComment = await testUtils.agent
.post('/flags/publication-problem-live-flag/comment')
.post('/flags/publication-problem-live-flag-1/comment')
.query({
apiKey: '000000003'
})
Expand All @@ -72,7 +72,7 @@ describe('Create flags comments on a flag', () => {

test('The body of the request is invalid', async () => {
const createFlagComment = await testUtils.agent
.post('/flags/publication-problem-live-flag/comment')
.post('/flags/publication-problem-live-flag-1/comment')
.query({
apiKey: '123456789'
})
Expand Down
74 changes: 74 additions & 0 deletions api/src/components/flag/__tests__/getUserFlags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as testUtils from 'lib/testUtils';

describe('Get flags created by a user', () => {
beforeAll(async () => {
await testUtils.clearDB();
await testUtils.testSeed();
});

test('Anonymous user can get flags created by a user', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags');
expect(getUserFlags.status).toEqual(200);
});

test('Returned flags are all unresolved by default', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags');
const flags = getUserFlags.body.data;

for (const flag of flags) {
expect(flag.resolved).toEqual(false);
}
});

test('Resolved flags can be included with includeResolved query param', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
includeResolved: true
});
const flags = getUserFlags.body.data;
expect(flags.some((flag: { resolved: boolean; [key: string]: any }) => flag.resolved)).toEqual(true);

Check warning on line 28 in api/src/components/flag/__tests__/getUserFlags.test.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
});

test('Invalid values for includeResolved param are rejected', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
includeResolved: 'Apple'
});
expect(getUserFlags.status).toEqual(400);
expect(getUserFlags.body.message).toHaveLength(1);
expect(getUserFlags.body.message[0].keyword).toEqual('type');
expect(getUserFlags.body.message[0].message).toEqual('must be boolean');
});

test('Unexpected query params are rejected', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
date: '2020-01-01'
});
expect(getUserFlags.status).toEqual(400);
expect(getUserFlags.body.message).toHaveLength(1);
expect(getUserFlags.body.message[0].keyword).toEqual('additionalProperties');
});

test('Results can be limited', async () => {
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
includeResolved: true,
limit: 1
});
expect(getUserFlags.status).toEqual(200);
expect(getUserFlags.body.data).toHaveLength(1);
expect(getUserFlags.body.metadata.total).toBeGreaterThan(1);
});

test('Results can be offset', async () => {
// Get default results first
const getUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
includeResolved: true
});
expect(getUserFlags.status).toEqual(200);

const getOffsetUserFlags = await testUtils.agent.get('/users/test-user-2/flags').query({
includeResolved: true,
offset: 1
});
expect(getOffsetUserFlags.status).toEqual(200);
expect(getOffsetUserFlags.body.data[0].id).toEqual(getUserFlags.body.data[1].id);
});
});
8 changes: 4 additions & 4 deletions api/src/components/flag/__tests__/resolveFlag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,31 @@ describe('Resolve a flag', () => {
});

test('The flagger can resolve the flag', async () => {
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag/resolve').query({
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag-1/resolve').query({
apiKey: '987654321'
});

expect(resolveFlag.status).toEqual(200);
});

test('Only the flagger or super user can resolve the flag', async () => {
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag/resolve').query({
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag-1/resolve').query({
apiKey: '123456789'
});

expect(resolveFlag.status).toEqual(403);
});

test('A super user can resolve the flag', async () => {
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag/resolve').query({
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag-1/resolve').query({
apiKey: '000000004'
});

expect(resolveFlag.status).toEqual(200);
});

test('An unrelated user cannot resolve a flag', async () => {
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag/resolve').query({
const resolveFlag = await testUtils.agent.post('/flags/publication-problem-live-flag-1/resolve').query({
apiKey: '000000003'
});

Expand Down
6 changes: 3 additions & 3 deletions api/src/components/flag/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as publicationService from 'publication/service';
import * as response from 'lib/response';
import * as userService from 'user/service';

export const get = async (event: I.APIRequest<undefined, undefined, I.GetFlagByID>): Promise<I.JSONResponse> => {
export const get = async (event: I.APIRequest<undefined, undefined, I.GetFlagPathParams>): Promise<I.JSONResponse> => {
try {
const flag = await flagService.get(event.pathParameters.id);

Expand All @@ -36,10 +36,10 @@ export const getPublicationFlags = async (
};

export const getUserFlags = async (
event: I.APIRequest<undefined, undefined, I.GetUserFlagsPathParams>
event: I.APIRequest<undefined, I.GetUserFlagsQueryParams, I.GetUserFlagsPathParams>
): Promise<I.JSONResponse> => {
try {
const flags = await flagService.getByUserID(event.pathParameters.userId);
const flags = await flagService.getByUserID(event.pathParameters.userId, event.queryStringParameters);

return response.json(200, flags);
} catch (err) {
Expand Down
3 changes: 2 additions & 1 deletion api/src/components/flag/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export const getPublicationFlags = middy(flagController.getPublicationFlags)

export const getUserFlags = middy(flagController.getUserFlags)
.use(middleware.doNotWaitForEmptyEventLoop({ runOnError: true, runOnBefore: true, runOnAfter: true }))
.use(middleware.httpJsonBodyParser());
.use(middleware.httpJsonBodyParser())
.use(middleware.validator(flagSchema.getUserFlags, 'queryStringParameters'));
26 changes: 26 additions & 0 deletions api/src/components/flag/schema/getUserFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as I from 'interface';

const getUserFlagsSchema: I.JSONSchemaType<I.GetUserFlagsQueryParams> = {
type: 'object',
properties: {
offset: {
type: 'number',
minimum: 0,
default: 0
},
limit: {
type: 'number',
minimum: 1,
default: 10
},
includeResolved: {
type: 'boolean',
default: false,
nullable: true
}
},
additionalProperties: false,
required: []
};

export default getUserFlagsSchema;
1 change: 1 addition & 0 deletions api/src/components/flag/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as createFlag } from './createFlag';
export { default as createFlagComment } from './createFlagComment';
export { default as getUserFlags } from './getUserFlags';
90 changes: 61 additions & 29 deletions api/src/components/flag/service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as client from 'lib/client';
import * as I from 'interface';
import { Prisma } from '@prisma/client';

export const get = (id: string) =>
client.prisma.publicationFlags.findFirst({
Expand Down Expand Up @@ -71,40 +72,71 @@ export const getByPublicationID = (id: string) =>
* This gets the flags created by a user
* keeping as this can be useful for profile pages
*/
export const getByUserID = (id: string) =>
client.prisma.publicationFlags.findMany({
include: {
user: {
select: {
id: true,
orcid: true,
firstName: true,
lastName: true,
createdAt: true,
updatedAt: true
}
},
flagComments: {
include: {
user: {
select: {
id: true,
orcid: true,
firstName: true,
lastName: true,
createdAt: true,
updatedAt: true
export const getByUserID = async (
id: string,
options: { limit: number; offset: number; includeResolved?: boolean }
) => {
const where: Prisma.PublicationFlagsWhereInput = {
user: {
id
},
...(options.includeResolved ? {} : { resolved: false })
};

const [flags, total] = await Promise.all([
client.prisma.publicationFlags.findMany({
where,
select: {
id: true,
category: true,
resolved: true,
createdAt: true,
publication: {
select: {
id: true,
type: true,
versions: {
where: {
isLatestLiveVersion: true
},
select: {
coAuthors: {
select: {
user: {
select: {
firstName: true,
lastName: true
}
}
}
},
content: true,
description: true,
publishedDate: true,
title: true
}
}
}
}
},
take: options.limit,
skip: options.offset,
orderBy: {
createdAt: 'desc'
}
},
where: {
user: {
id
}
}),
client.prisma.publicationFlags.count({ where })
]);

return {
data: flags,
metadata: {
total,
limit: options.limit,
offset: options.offset
}
});
};
};

export const createFlag = (
publication: string,
Expand Down
Loading
Loading