Skip to content

Commit

Permalink
Merge pull request #1337 from SciCatProject/UI-604
Browse files Browse the repository at this point in the history
Enable FiltersConfig and ScientificConditions on the BE
  • Loading branch information
Ingvord authored Aug 21, 2024
2 parents 23b6906 + d6f93bd commit 9216dea
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 2 deletions.
11 changes: 11 additions & 0 deletions src/config/default-filters.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{ "type": "LocationFilterComponent", "visible": true },
{ "type": "PidFilterComponent", "visible": true },
{ "type": "PidFilterContainsComponent", "visible": false },
{ "type": "PidFilterStartsWithComponent", "visible": false },
{ "type": "GroupFilterComponent", "visible": true },
{ "type": "TypeFilterComponent", "visible": true },
{ "type": "KeywordFilterComponent", "visible": true },
{ "type": "DateRangeFilterComponent", "visible": true },
{ "type": "TextFilterComponent", "visible": true }
]
2 changes: 2 additions & 0 deletions src/users/dto/create-user-settings.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ApiProperty } from "@nestjs/swagger";
import { UpdateUserSettingsDto } from "./update-user-settings.dto";
import { IsString } from "class-validator";

export class CreateUserSettingsDto extends UpdateUserSettingsDto {
@ApiProperty({ type: String, required: true })
@IsString()
readonly userId: string;
}
16 changes: 16 additions & 0 deletions src/users/dto/update-user-settings.dto.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { ApiProperty, PartialType } from "@nestjs/swagger";
import {
FilterConfig,
ScientificCondition,
} from "../schemas/user-settings.schema";
import { IsArray, IsNumber } from "class-validator";

export class UpdateUserSettingsDto {
@ApiProperty()
@IsArray()
readonly columns: Record<string, unknown>[];

@ApiProperty({ type: Number, required: false, default: 25 })
@IsNumber()
readonly datasetCount?: number;

@ApiProperty({ type: Number, required: false, default: 25 })
@IsNumber()
readonly jobCount?: number;

@ApiProperty()
@IsArray()
readonly filters: FilterConfig[];

@ApiProperty()
@IsArray()
readonly conditions: ScientificCondition[];
}

export class PartialUpdateUserSettingsDto extends PartialType(
Expand Down
3 changes: 3 additions & 0 deletions src/users/interceptors/create-user-settings.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Observable, tap } from "rxjs";
import { CreateUserSettingsDto } from "../dto/create-user-settings.dto";
import { UsersService } from "../users.service";
import { FILTER_CONFIGS } from "../schemas/user-settings.schema";

@Injectable()
export class CreateUserSettingsInterceptor implements NestInterceptor {
Expand All @@ -34,6 +35,8 @@ export class CreateUserSettingsInterceptor implements NestInterceptor {
const createUserSettingsDto: CreateUserSettingsDto = {
userId,
columns: [],
filters: FILTER_CONFIGS,
conditions: [],
};
return this.usersService.createUserSettings(
userId,
Expand Down
33 changes: 33 additions & 0 deletions src/users/interceptors/default-user-settings.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
CallHandler,
ExecutionContext,
Injectable,
Logger,
NestInterceptor,
} from "@nestjs/common";
import { map, Observable } from "rxjs";
import { UsersService } from "../users.service";
import { FILTER_CONFIGS } from "../schemas/user-settings.schema";
import { UpdateUserSettingsDto } from "../dto/update-user-settings.dto";

@Injectable()
export class DefaultUserSettingsInterceptor implements NestInterceptor {
constructor(private usersService: UsersService) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<unknown>> {
return next.handle().pipe(
map(async () => {
Logger.log("DefaultUserSettingsInterceptor");
const defaultUserSettings: UpdateUserSettingsDto = {
columns: [],
filters: FILTER_CONFIGS,
conditions: [],
};
console.log(defaultUserSettings);
return defaultUserSettings;
}),
);
}
}
47 changes: 47 additions & 0 deletions src/users/schemas/user-settings.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,37 @@ import * as mongoose from "mongoose";
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { ApiProperty } from "@nestjs/swagger";
import { Document } from "mongoose";
import filterConfigs from "../../config/default-filters.config.json";

export type UserSettingsDocument = UserSettings & Document;

// Define possible filter component types as a union of string literals
export type FilterComponentType =
| "LocationFilterComponent"
| "PidFilterComponent"
| "PidFilterContainsComponent"
| "PidFilterStartsWithComponent"
| "GroupFilterComponent"
| "TypeFilterComponent"
| "KeywordFilterComponent"
| "DateRangeFilterComponent"
| "TextFilterComponent";

// Define the Filter interface
export interface FilterConfig {
type: FilterComponentType;
visible: boolean;
}

// Define the Condition interface
export interface ScientificCondition {
field: string;
value: string;
operator: string;
}

export const FILTER_CONFIGS: FilterConfig[] = filterConfigs as FilterConfig[];

@Schema({
collection: "UserSetting",
toJSON: {
Expand Down Expand Up @@ -43,6 +71,25 @@ export class UserSettings {
@ApiProperty({ type: String, required: true })
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: "User", required: true })
userId: string;

@ApiProperty({
type: [Object],
default: FILTER_CONFIGS,
description: "Array of filters the user has set",
})
@Prop({
type: [{ type: Object }],
default: FILTER_CONFIGS,
})
filters: FilterConfig[];

@ApiProperty({
type: [Object],
default: [],
description: "Array of conditions the user has set",
})
@Prop({ type: [{ type: Object }], default: [] })
conditions: ScientificCondition[];
}

export const UserSettingsSchema = SchemaFactory.createForClass(UserSettings);
75 changes: 75 additions & 0 deletions src/users/users.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,43 @@ import { AuthService } from "src/auth/auth.service";
import { CaslModule } from "src/casl/casl.module";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
import { UpdateUserSettingsDto } from "./dto/update-user-settings.dto";

class UsersServiceMock {
findByIdUserIdentity(id: string) {
return { id };
}

async findByIdUserSettings(userId: string) {
return mockUserSettings;
}

async findOneAndUpdateUserSettings(
userId: string,
updateUserSettingsDto: UpdateUserSettingsDto,
) {
return { ...updateUserSettingsDto, _id: userId };
}
}

const mockUserSettings = {
_id: "user1",
userId: "user1",
columns: [],
datasetCount: 25,
jobCount: 25,
filters: [
{ type: "LocationFilterComponent", visible: true },
{ type: "PidFilterComponent", visible: true },
],
conditions: [{ field: "status", value: "active", operator: "equals" }],
};

class AuthServiceMock {}

describe("UsersController", () => {
let controller: UsersController;
let usersService: UsersService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -26,9 +52,58 @@ describe("UsersController", () => {
}).compile();

controller = module.get<UsersController>(UsersController);
usersService = module.get<UsersService>(UsersService);

// bypass authorization
jest
.spyOn(controller as UsersController, "checkUserAuthorization")
.mockImplementation(() => Promise.resolve());
});

it("should be defined", () => {
expect(controller).toBeDefined();
});

it("should return user settings with filters and conditions", async () => {
jest
.spyOn(usersService, "findByIdUserSettings")
.mockResolvedValue(mockUserSettings);

const userId = "user1";
const result = await controller.getSettings(
{ user: { _id: userId } },
userId,
);

expect(result).toEqual(mockUserSettings);
expect(result.filters).toBeDefined();
expect(result.filters.length).toBeGreaterThan(0);
expect(result.conditions).toBeDefined();
expect(result.conditions.length).toBeGreaterThan(0);
});

it("should update user settings with filters and conditions", async () => {
const updatedSettings = {
...mockUserSettings,
filters: [{ type: "PidFilterContainsComponent", visible: false }],
conditions: [{ field: "status", value: "inactive", operator: "equals" }],
};

jest
.spyOn(usersService, "findOneAndUpdateUserSettings")
.mockResolvedValue(updatedSettings);

const userId = "user-id";
const result = await controller.updateSettings(
{ user: { _id: userId } },
userId,
updatedSettings,
);

expect(result).toEqual(updatedSettings);
expect(result.filters).toBeDefined();
expect(result.filters.length).toBe(1);
expect(result.conditions).toBeDefined();
expect(result.conditions.length).toBe(1);
});
});
24 changes: 23 additions & 1 deletion src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Body,
ForbiddenException,
HttpCode,
CanActivate,
} from "@nestjs/common";
import {
ApiBearerAuth,
Expand All @@ -31,7 +32,10 @@ import { Request } from "express";
import { JWTUser } from "../auth/interfaces/jwt-user.interface";
import { UserSettings } from "./schemas/user-settings.schema";
import { CreateUserSettingsDto } from "./dto/create-user-settings.dto";
import { PartialUpdateUserSettingsDto } from "./dto/update-user-settings.dto";
import {
PartialUpdateUserSettingsDto,
UpdateUserSettingsDto,
} from "./dto/update-user-settings.dto";
import { User } from "./schemas/user.schema";
import { CreateUserSettingsInterceptor } from "./interceptors/create-user-settings.interceptor";
import { AuthService } from "src/auth/auth.service";
Expand All @@ -44,6 +48,8 @@ import { CreateCustomJwt } from "./dto/create-custom-jwt.dto";
import { AuthenticatedPoliciesGuard } from "../casl/guards/auth-check.guard";
import { ReturnedUserDto } from "./dto/returned-user.dto";
import { ReturnedAuthLoginDto } from "src/auth/dto/returnedLogin.dto";
import { PoliciesGuard } from "src/casl/guards/policies.guard";
import { DefaultUserSettingsInterceptor } from "./interceptors/default-user-settings.interceptor";

@ApiBearerAuth()
@ApiTags("users")
Expand Down Expand Up @@ -307,6 +313,22 @@ export class UsersController {
return this.usersService.findOneAndDeleteUserSettings(id);
}

@UseInterceptors(DefaultUserSettingsInterceptor)
@UseGuards(
class ByPassAuthenticatedPoliciesGuard
extends PoliciesGuard
implements CanActivate
{
async canActivate(): Promise<boolean> {
return Promise.resolve(true);
}
},
)
@Get("/settings/default")
async getDefaultSettings(): Promise<UserSettings> {
return Promise.resolve(new UserSettings());
}

@UseGuards(AuthenticatedPoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
return (
Expand Down
24 changes: 24 additions & 0 deletions src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ const mockUser: User = {
datasetCount: 25,
jobCount: 25,
userId: "testUserId",
filters: [
{ type: "LocationFilterComponent", visible: true },
{ type: "PidFilterComponent", visible: true },
{ type: "PidFilterContainsComponent", visible: false },
{ type: "PidFilterStartsWithComponent", visible: false },
{ type: "GroupFilterComponent", visible: true },
{ type: "TypeFilterComponent", visible: true },
{ type: "KeywordFilterComponent", visible: true },
{ type: "DateRangeFilterComponent", visible: true },
{ type: "TextFilterComponent", visible: true },
],
conditions: [],
},
};

Expand Down Expand Up @@ -58,6 +70,18 @@ const mockUserSettings: UserSettings = {
datasetCount: 25,
jobCount: 25,
userId: "testUserId",
filters: [
{ type: "LocationFilterComponent", visible: true },
{ type: "PidFilterComponent", visible: true },
{ type: "PidFilterContainsComponent", visible: false },
{ type: "PidFilterStartsWithComponent", visible: false },
{ type: "GroupFilterComponent", visible: true },
{ type: "TypeFilterComponent", visible: true },
{ type: "KeywordFilterComponent", visible: true },
{ type: "DateRangeFilterComponent", visible: true },
{ type: "TextFilterComponent", visible: true },
],
conditions: [],
};

describe("UsersService", () => {
Expand Down
4 changes: 3 additions & 1 deletion test/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("2360: Users settings", () => {
accessTokenUser1 = loginResponseUser1.token;
});

it("0010: Update users settings with valid value should sucess ", async () => {
it("0010: Update users settings with valid value should success ", async () => {
return request(appUrl)
.put(`/api/v3/Users/${userIdUser1}/settings`)
.set("Accept", "application/json")
Expand All @@ -58,6 +58,8 @@ describe("2360: Users settings", () => {
res.body.should.have.property("userId", userIdUser1);
res.body.should.have.property("datasetCount");
res.body.should.have.property("jobCount");
res.body.should.have.property("filters");
res.body.should.have.property("conditions");
});
});
});

0 comments on commit 9216dea

Please sign in to comment.