diff --git a/db_patches/0124_AddIsActiveCallFlag.sql b/db_patches/0124_AddIsActiveCallFlag.sql new file mode 100644 index 000000000..2d67be111 --- /dev/null +++ b/db_patches/0124_AddIsActiveCallFlag.sql @@ -0,0 +1,12 @@ +DO +$$ +BEGIN + IF register_patch('AddIsActiveCallFlag.sql', 'Martin Trajanovski', 'Add is_active flag to the calls table', '2022-06-21') THEN + + ALTER TABLE "call" + ADD COLUMN is_active boolean DEFAULT true; + + END IF; +END; +$$ +LANGUAGE plpgsql; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 398cf647b..5608ab3ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ "@user-office-software/duo-logger": "^2.0.2", "@user-office-software/duo-message-broker": "^1.3.0", "@user-office-software/duo-validation": "^3.2.0", - "apollo-graphql": "^0.9.5", + "apollo-graphql": "^0.9.7", "apollo-server-core": "^3.9.0", - "apollo-server-express": "^3.6.3", + "apollo-server-express": "^3.9.0", "await-to-js": "^2.1.1", "axios": "^0.26.0", "bcryptjs": "^2.4.3", @@ -1779,9 +1779,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "version": "4.17.29", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", + "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2958,9 +2958,9 @@ } }, "node_modules/apollo-graphql": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.5.tgz", - "integrity": "sha512-RGt5k2JeBqrmnwRM0VOgWFiGKlGJMfmiif/4JvdaEqhMJ+xqe/9cfDYzXfn33ke2eWixsAbjEbRfy8XbaN9nTw==", + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.7.tgz", + "integrity": "sha512-bezL9ItUWUGHTm1bI/XzIgiiZbhXpsC7uxk4UxFPmcVJwJsDc3ayZ99oXxAaK+3Rbg/IoqrHckA6CwmkCsbaSA==", "dependencies": { "core-js-pure": "^3.10.2", "lodash.sortby": "^4.7.0", @@ -2981,17 +2981,6 @@ "@apollo/protobufjs": "1.2.2" } }, - "node_modules/apollo-server-caching": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", - "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12.0" - } - }, "node_modules/apollo-server-core": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.9.0.tgz", @@ -3166,23 +3155,6 @@ "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/apollo-server-core/node_modules/apollo-server-types": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.6.1.tgz", - "integrity": "sha512-XOPlBlRdwP00PrG03OffGGWuzyei+J9t1rAnvyHsSdP0JCgQWigHJfvL1N9Bhgi4UTjl9JadKOJh1znLNlqIFQ==", - "dependencies": { - "@apollo/utils.keyvaluecache": "^1.0.1", - "@apollo/utils.logger": "^1.0.0", - "apollo-reporting-protobuf": "^3.3.1", - "apollo-server-env": "^4.2.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, "node_modules/apollo-server-core/node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -3203,18 +3175,18 @@ } }, "node_modules/apollo-server-express": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.3.tgz", - "integrity": "sha512-3CjahZ+n+1T7pHH1qW1B6Ns0BzwOMeupAp2u0+M8ruOmE/e7VKn0OSOQQckZ8Z2AcWxWeno9K89fIv3PoSYgYA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.9.0.tgz", + "integrity": "sha512-scSeHy9iB7W3OiF3uLQEzad9Jm9tEfDF8ACsJb2P+xX69uqg6zizsrQvj3qRhazCO7FKMcMu9zQFR0hy7zKbUA==", "dependencies": { "@types/accepts": "^1.3.5", "@types/body-parser": "1.19.2", "@types/cors": "2.8.12", "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.28", + "@types/express-serve-static-core": "4.17.29", "accepts": "^1.3.5", - "apollo-server-core": "^3.6.3", - "apollo-server-types": "^3.5.1", + "apollo-server-core": "^3.9.0", + "apollo-server-types": "^3.6.1", "body-parser": "^1.19.0", "cors": "^2.8.5", "parseurl": "^1.3.3" @@ -3228,12 +3200,13 @@ } }, "node_modules/apollo-server-types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.1.tgz", - "integrity": "sha512-zG7xLl4mmHuZMAYOfjWKHY/IC/GgIkJ3HnYuR7FRrnPpRA9Yt5Kf1M1rjm1Esuqzpb/dt8pM7cX40QaIQObCYQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.6.1.tgz", + "integrity": "sha512-XOPlBlRdwP00PrG03OffGGWuzyei+J9t1rAnvyHsSdP0JCgQWigHJfvL1N9Bhgi4UTjl9JadKOJh1znLNlqIFQ==", "dependencies": { - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "apollo-reporting-protobuf": "^3.3.1", "apollo-server-env": "^4.2.1" }, "engines": { @@ -14155,9 +14128,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "version": "4.17.29", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", + "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -15029,9 +15002,9 @@ } }, "apollo-graphql": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.5.tgz", - "integrity": "sha512-RGt5k2JeBqrmnwRM0VOgWFiGKlGJMfmiif/4JvdaEqhMJ+xqe/9cfDYzXfn33ke2eWixsAbjEbRfy8XbaN9nTw==", + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.7.tgz", + "integrity": "sha512-bezL9ItUWUGHTm1bI/XzIgiiZbhXpsC7uxk4UxFPmcVJwJsDc3ayZ99oXxAaK+3Rbg/IoqrHckA6CwmkCsbaSA==", "requires": { "core-js-pure": "^3.10.2", "lodash.sortby": "^4.7.0", @@ -15046,14 +15019,6 @@ "@apollo/protobufjs": "1.2.2" } }, - "apollo-server-caching": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", - "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, "apollo-server-core": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.9.0.tgz", @@ -15171,17 +15136,6 @@ "apollo-server-types": "^3.6.1" } }, - "apollo-server-types": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.6.1.tgz", - "integrity": "sha512-XOPlBlRdwP00PrG03OffGGWuzyei+J9t1rAnvyHsSdP0JCgQWigHJfvL1N9Bhgi4UTjl9JadKOJh1znLNlqIFQ==", - "requires": { - "@apollo/utils.keyvaluecache": "^1.0.1", - "@apollo/utils.logger": "^1.0.0", - "apollo-reporting-protobuf": "^3.3.1", - "apollo-server-env": "^4.2.1" - } - }, "whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -15198,30 +15152,31 @@ } }, "apollo-server-express": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.3.tgz", - "integrity": "sha512-3CjahZ+n+1T7pHH1qW1B6Ns0BzwOMeupAp2u0+M8ruOmE/e7VKn0OSOQQckZ8Z2AcWxWeno9K89fIv3PoSYgYA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.9.0.tgz", + "integrity": "sha512-scSeHy9iB7W3OiF3uLQEzad9Jm9tEfDF8ACsJb2P+xX69uqg6zizsrQvj3qRhazCO7FKMcMu9zQFR0hy7zKbUA==", "requires": { "@types/accepts": "^1.3.5", "@types/body-parser": "1.19.2", "@types/cors": "2.8.12", "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.28", + "@types/express-serve-static-core": "4.17.29", "accepts": "^1.3.5", - "apollo-server-core": "^3.6.3", - "apollo-server-types": "^3.5.1", + "apollo-server-core": "^3.9.0", + "apollo-server-types": "^3.6.1", "body-parser": "^1.19.0", "cors": "^2.8.5", "parseurl": "^1.3.3" } }, "apollo-server-types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.1.tgz", - "integrity": "sha512-zG7xLl4mmHuZMAYOfjWKHY/IC/GgIkJ3HnYuR7FRrnPpRA9Yt5Kf1M1rjm1Esuqzpb/dt8pM7cX40QaIQObCYQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.6.1.tgz", + "integrity": "sha512-XOPlBlRdwP00PrG03OffGGWuzyei+J9t1rAnvyHsSdP0JCgQWigHJfvL1N9Bhgi4UTjl9JadKOJh1znLNlqIFQ==", "requires": { - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "apollo-reporting-protobuf": "^3.3.1", "apollo-server-env": "^4.2.1" } }, diff --git a/package.json b/package.json index 9adfe27bc..83bbb050f 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "@user-office-software/duo-logger": "^2.0.2", "@user-office-software/duo-message-broker": "^1.3.0", "@user-office-software/duo-validation": "^3.2.0", - "apollo-graphql": "^0.9.5", + "apollo-graphql": "^0.9.7", "apollo-server-core": "^3.9.0", - "apollo-server-express": "^3.6.3", + "apollo-server-express": "^3.9.0", "await-to-js": "^2.1.1", "axios": "^0.26.0", "bcryptjs": "^2.4.3", diff --git a/src/auth/ProposalAuthorization.ts b/src/auth/ProposalAuthorization.ts index 480787ec1..4d5facaae 100644 --- a/src/auth/ProposalAuthorization.ts +++ b/src/auth/ProposalAuthorization.ts @@ -196,7 +196,7 @@ export class ProposalAuthorization { private async isProposalEditable(proposal: Proposal): Promise { const callId = proposal.callId; - const isCallActive = await this.callDataSource.checkActiveCall(callId); + const isCallEnded = await this.callDataSource.isCallEnded(callId); const proposalStatus = ( await this.proposalSettingsDataSource.getProposalStatus(proposal.statusId) )?.shortCode; @@ -205,10 +205,10 @@ export class ProposalAuthorization { return true; } - if (isCallActive) { - return proposalStatus === ProposalStatusDefaultShortCodes.DRAFT; - } else { + if (isCallEnded) { return false; + } else { + return proposalStatus === ProposalStatusDefaultShortCodes.DRAFT; } } diff --git a/src/datasources/CallDataSource.ts b/src/datasources/CallDataSource.ts index 94dba38e1..c0b1831c9 100644 --- a/src/datasources/CallDataSource.ts +++ b/src/datasources/CallDataSource.ts @@ -18,5 +18,5 @@ export interface CallDataSource { args: RemoveAssignedInstrumentFromCallInput ): Promise; getCallsByInstrumentScientist(scientistId: number): Promise; - checkActiveCall(callId: number): Promise; + isCallEnded(callId: number): Promise; } diff --git a/src/datasources/mockups/CallDataSource.ts b/src/datasources/mockups/CallDataSource.ts index 0b32fa260..4199d6ced 100644 --- a/src/datasources/mockups/CallDataSource.ts +++ b/src/datasources/mockups/CallDataSource.ts @@ -35,7 +35,8 @@ export const dummyCallFactory = (values?: Partial) => { values?.esiTemplateId || 2, values?.allocationTimeUnit || AllocationTimeUnits.Day, values?.title || 'Title', - values?.description || 'Description' + values?.description || 'Description', + values?.isActive || true ); }; @@ -65,7 +66,8 @@ export const dummyCall = new Call( 2, AllocationTimeUnits.Day, '', - '' + '', + true ); export const anotherDummyCall = new Call( @@ -94,7 +96,8 @@ export const anotherDummyCall = new Call( 2, AllocationTimeUnits.Day, '', - '' + '', + true ); export const dummyCalls = [dummyCall, anotherDummyCall]; @@ -153,7 +156,7 @@ export class CallDataSourceMock implements CallDataSource { return dummyCalls; } - async checkActiveCall(callId: number): Promise { - return callId === 1; + async isCallEnded(callId: number): Promise { + return callId !== 1; } } diff --git a/src/datasources/postgres/CallDataSource.ts b/src/datasources/postgres/CallDataSource.ts index 7de0b69fb..8504d0bce 100644 --- a/src/datasources/postgres/CallDataSource.ts +++ b/src/datasources/postgres/CallDataSource.ts @@ -42,21 +42,25 @@ export default class PostgresCallDataSource implements CallDataSource { // if filter is explicitly set to true or false if (filter?.isActive === true) { - const currentDate = new Date().toISOString(); - query - .where('start_call', '<=', currentDate) - .andWhere('end_call', '>=', currentDate); + query.where('is_active', true); } else if (filter?.isActive === false) { - const currentDate = new Date().toISOString(); - query - .where('start_call', '>=', currentDate) - .orWhere('end_call', '<=', currentDate); + query.where('is_active', false); } + /** + * NOTE: We are comparing dates instead of using the call_ended flag, + * because the flag is set once per hour and we could have a gap. + * TODO: Maybe there is a need to use the timezone setting here but not quite sure about it. Discussion is needed here! + */ + const currentDate = new Date().toISOString(); if (filter?.isEnded === true) { - query.where('call_ended', true); + query + .where('start_call', '>=', currentDate) + .andWhere('end_call', '<=', currentDate); } else if (filter?.isEnded === false) { - query.where('call_ended', false); + query + .where('start_call', '<=', currentDate) + .andWhere('end_call', '>=', currentDate); } if (filter?.isReviewEnded === true) { @@ -233,6 +237,7 @@ export default class PostgresCallDataSource implements CallDataSource { allocation_time_unit: args.allocationTimeUnit, title: args.title, description: args.description, + is_active: args.isActive, }, ['*'] ) @@ -315,7 +320,7 @@ export default class PostgresCallDataSource implements CallDataSource { return records.map(createCallObject); } - public async checkActiveCall(callId: number): Promise { + public async isCallEnded(callId: number): Promise { const currentDate = new Date().toISOString(); return database @@ -325,6 +330,6 @@ export default class PostgresCallDataSource implements CallDataSource { .andWhere('end_call', '>=', currentDate) .andWhere('call_id', '=', callId) .first() - .then((call: CallRecord) => (call ? true : false)); + .then((call: CallRecord) => (call ? false : true)); } } diff --git a/src/datasources/postgres/records.ts b/src/datasources/postgres/records.ts index 64f81ad5a..fb36899f0 100644 --- a/src/datasources/postgres/records.ts +++ b/src/datasources/postgres/records.ts @@ -289,6 +289,7 @@ export interface CallRecord { readonly allocation_time_unit: AllocationTimeUnits; readonly title: string; readonly description: string; + readonly is_active: boolean; } export interface PageTextRecord { @@ -845,7 +846,8 @@ export const createCallObject = (call: CallRecord) => { call.esi_template_id, call.allocation_time_unit, call.title, - call.description + call.description, + call.is_active ); }; diff --git a/src/models/Call.ts b/src/models/Call.ts index 085ca4116..258f44ecf 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -25,7 +25,8 @@ export class Call { public esiTemplateId: number | undefined, public allocationTimeUnit: AllocationTimeUnits, public title: string, - public description: string + public description: string, + public isActive: boolean ) {} } diff --git a/src/mutations/CallMutations.spec.ts b/src/mutations/CallMutations.spec.ts index e85a1425b..629762951 100644 --- a/src/mutations/CallMutations.spec.ts +++ b/src/mutations/CallMutations.spec.ts @@ -142,6 +142,7 @@ describe('Test Call Mutations', () => { callReviewEnded: false, callSEPReviewEnded: false, templateId: 1, + isActive: true, }); }); @@ -198,6 +199,7 @@ describe('Test Call Mutations', () => { templateId: 1, title: 'Title', description: 'Description', + isActive: true, }; return expect( diff --git a/src/mutations/ProposalMutations.ts b/src/mutations/ProposalMutations.ts index f489bad58..c1d544edd 100644 --- a/src/mutations/ProposalMutations.ts +++ b/src/mutations/ProposalMutations.ts @@ -67,7 +67,7 @@ export default class ProposalMutations { { callId }: { callId: number } ): Promise { // Check if there is an open call - if (!(await this.callDataSource.checkActiveCall(callId))) { + if (await this.callDataSource.isCallEnded(callId)) { return rejection('Call is not active', { callId, agent }); } @@ -216,10 +216,8 @@ export default class ProposalMutations { } // Check if there is an open call - const hasActiveCall = await this.callDataSource.checkActiveCall( - proposal.callId - ); - if (!isUserOfficer && !hasActiveCall) { + const isCallEnded = await this.callDataSource.isCallEnded(proposal.callId); + if (!isUserOfficer && isCallEnded) { return rejection('Can not submit proposal because call is not active', { agent, proposalPk, @@ -501,7 +499,7 @@ export default class ProposalMutations { } // Check if there is an open call - if (!(await this.callDataSource.checkActiveCall(callId))) { + if (await this.callDataSource.isCallEnded(callId)) { return rejection( 'Can not clone proposal because the call is not active', { callId, agent, sourceProposal } diff --git a/src/resolvers/mutations/UpdateCallMutation.ts b/src/resolvers/mutations/UpdateCallMutation.ts index 63b9608e1..867eec9e1 100644 --- a/src/resolvers/mutations/UpdateCallMutation.ts +++ b/src/resolvers/mutations/UpdateCallMutation.ts @@ -93,6 +93,9 @@ export class UpdateCallInput { @Field({ nullable: true }) public description: string; + @Field(() => Boolean, { nullable: true }) + public isActive?: boolean; + @Field(() => [Int], { nullable: true }) public seps?: number[]; } diff --git a/src/resolvers/types/Call.ts b/src/resolvers/types/Call.ts index b9efe2501..25f9bab93 100644 --- a/src/resolvers/types/Call.ts +++ b/src/resolvers/types/Call.ts @@ -89,6 +89,9 @@ export class Call implements Partial { @Field({ nullable: true }) public description: string; + + @Field(() => Boolean) + public isActive: boolean; } @Resolver(() => Call) @@ -123,15 +126,6 @@ export class CallInstrumentsResolver { async proposalCount(@Root() call: Call, @Ctx() context: ResolverContext) { return context.queries.proposal.dataSource.getCount(call.id); } - - @FieldResolver(() => Boolean) - isActive(@Root() call: Call): boolean { - const now = new Date(); - const startCall = new Date(call.startCall); - const endCall = new Date(call.endCall); - - return startCall <= now && endCall >= now; - } } // eslint-disable-next-line @typescript-eslint/no-explicit-any