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

feat(dal,api): strict null check and type fixes #2893

Merged
merged 11 commits into from
Mar 2, 2023

Conversation

LetItRock
Copy link
Contributor

@LetItRock LetItRock commented Feb 23, 2023

What change does this PR introduce?

This is the second part, the previous is #2890.
Here I've enabled the strict null check flag in the TS build config for DAL and API, resulting in many null/undefined errors in the API app.
Solved ~300 TS issues.

Some of these errors were visible in the Sentry, ex. here: https://novu-r9.sentry.io/issues/3841226005/?project=6248811&query=is%3Aunresolved&referrer=issue-stream

Why was this change needed?

Other information (Screenshots)

@LetItRock LetItRock self-assigned this Feb 23, 2023
@linear
Copy link

linear bot commented Feb 23, 2023

NV-1753 The repository function `findById` has wrong return type

Why? (Context)

The repository function findById has wrong return type and is leading to the Cannot read properties of null (reading 'FIELD') error across the API app if there is no check for null.

What?

Revisit all the repositories.

Definition of Done

The return type should be like Entity | null and we should add the missed checks across the API app.

@@ -20,7 +20,11 @@ export class SwitchEnvironment {
}

const member = await this.memberRepository.findMemberByUserId(command.organizationId, command.userId);
if (!member) throw new NotFoundException('Member is not found');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mainly in a lot of places I've added these kind of checks, because the repository function now returns Entity | null

@@ -18,7 +18,7 @@ export class PasswordResetRequest {
async execute(command: PasswordResetRequestCommand): Promise<{ success: boolean }> {
const email = normalizeEmail(command.email);
const foundUser = await this.userRepository.findByEmail(email);
if (foundUser) {
if (foundUser && foundUser.email) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a reset use-case and user.email should exist

Comment on lines -12 to -16
const config: {
applicationId: string;
clientId: string;
secretKey: string;
} = { applicationId: credentials.applicationId, clientId: credentials.clientId, secretKey: credentials.secretKey };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these were actually not used in the SlackProvider

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot.

@@ -9,5 +9,5 @@ export interface IChatHandler {
}

export interface IChatFactory {
getHandler(integration: IntegrationEntity): IChatHandler;
getHandler(integration: IntegrationEntity): IChatHandler | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

email, push, sms, chat factories might not return the handler, so this is the correct return type

Comment on lines 16 to 23
if (!currentJob) throw new ApiException('Digest job is not found');

const amount =
typeof currentJob?.digest.amount === 'number'
? currentJob?.digest.amount
: parseInt(currentJob.digest.amount, 10);
typeof currentJob.digest?.amount === 'number'
? currentJob.digest.amount
: parseInt(currentJob.digest?.amount ?? '0', 10);
const earliest = sub(new Date(currentJob.createdAt), {
[currentJob.digest.unit]: amount,
[currentJob.digest?.unit ?? DigestUnitEnum.MINUTES]: amount,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there were multiple TS errors, I've added default values, hope this solution is correct ;D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't do defaults here as would be confusing for the user if for any reason or bug they have selected hours or days and the digest gets executed based in minutes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@p-fernandez so what's your suggestion? I can add the // @ts-ignore comments...
I'll revert it because it works with NaN and undefined preserving the date:
Screenshot 2023-02-24 at 10 50 59

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should throw an exception to be honest. But it is not responsibility of this PR.
Please add the ignore comment and a TODO, please. 🙏🏻

Comment on lines +30 to +41
if (!currentTrigger) {
this.createExecutionDetails.execute(
CreateExecutionDetailsCommand.create({
...CreateExecutionDetailsCommand.getDetailsFromJob(currentJob),
detail: DetailEnum.DIGEST_TRIGGERED_EVENTS,
source: ExecutionDetailsSourceEnum.INTERNAL,
status: ExecutionDetailsStatusEnum.FAILED,
isTest: false,
isRetry: false,
})
);
throw new ApiException('Trigger job is not found');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be possible, but in the case

@@ -90,7 +90,7 @@ export class UpdateLayoutUseCase {
private mapToEntity(layout: LayoutDto): LayoutEntity {
return {
...layout,
_id: layout._id,
_id: layout._id as string,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't want to fix this issue here because it requires a lot of changes

@@ -37,7 +37,7 @@ export class CreateMessageTemplate {
actor: command.actor,
});

item = await this.messageTemplateRepository.findById(item._id);
item = (await this.messageTemplateRepository.findById(item._id)) as MessageTemplateEntity;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the message template is created above

Comment on lines +31 to +32
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mailObject: any = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also wasn't able to fix the TS issues here

@@ -77,7 +77,7 @@ export class BaseRepository<T_DBModel, T_MappedEntity> {
})
.batchSize(batchSize)
.cursor()) {
yield this.mapEntities(doc);
yield this.mapEntity(doc);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the doc is a single entity

@@ -7,7 +7,7 @@ import { MSTeamsHandler } from './handlers/msteams.handler';
export class ChatFactory implements IChatFactory {
handlers: IChatHandler[] = [new SlackHandler(), new DiscordHandler(), new MSTeamsHandler()];

getHandler(integration: IntegrationEntity): IChatHandler {
getHandler(integration: IntegrationEntity) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind removing some of the return types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the functions are defined in the interface IChatFactory, so you don't have to redefine them here

Comment on lines -12 to -16
const config: {
applicationId: string;
clientId: string;
secretKey: string;
} = { applicationId: credentials.applicationId, clientId: credentials.clientId, secretKey: credentials.secretKey };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot.

@@ -15,7 +15,7 @@ export abstract class BaseChatHandler implements IChatHandler {

async send(chatContent: IChatOptions) {
if (process.env.NODE_ENV === 'test') {
return null;
return {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I particularly do not like this. Returning an empty object I think it can cause a lot of problems and doesn't make much sense, to be honest.

Copy link
Contributor Author

@LetItRock LetItRock Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not an empty object then I'll have to add null to the return type and do additional checks in the code for that null, and null is only required for the tests, so to me it feels odd.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are in different hills 😂 .
But having to return anything I'd go for undefined for the same reasons I mentioned in a different comment. Also because return; returns undefined.
Your PR, your choice, though. 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this empty object is fine for now, it's only returned in testing env...

apps/api/src/app/shared/shared.module.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@p-fernandez p-fernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌟
Big big work. Thank you for that.

@@ -7,7 +7,7 @@ export class CreateLog {
constructor(private logRepository: LogRepository) {}

async execute(command: CreateLogCommand): Promise<LogEntity> {
let rawData: string = null;
let rawData: string | null = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I truly thought TS would interpret it as undefined type. Wow, my bad. 😓

I know I am insisting a bit on the issue in a different commands but not keen on using null. It is not directly passing to JSON.stringify but JSON.stringify(null) becomes 'null' while JSON.stringify(undefined) becomes undefined. In this case it would be in the command.raw but if arrives to rawData we can have problems thinking that could get an empty value while it could hold null stringified.
Just wanted to highlight those risks of using null because they can lead to unexpected bugs. So apologies for the repetition. 🙏🏻

Comment on lines 16 to 23
if (!currentJob) throw new ApiException('Digest job is not found');

const amount =
typeof currentJob?.digest.amount === 'number'
? currentJob?.digest.amount
: parseInt(currentJob.digest.amount, 10);
typeof currentJob.digest?.amount === 'number'
? currentJob.digest.amount
: parseInt(currentJob.digest?.amount ?? '0', 10);
const earliest = sub(new Date(currentJob.createdAt), {
[currentJob.digest.unit]: amount,
[currentJob.digest?.unit ?? DigestUnitEnum.MINUTES]: amount,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should throw an exception to be honest. But it is not responsibility of this PR.
Please add the ignore comment and a TODO, please. 🙏🏻

@@ -19,5 +19,5 @@ export class CreateIntegrationCommand extends EnvironmentCommand {
check: boolean;

@IsOptional()
userId?: string;
userId: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then let's remove the isOptional. 💪🏻

Base automatically changed from nv-1753-dal-update-repository-schema-and-model to next March 1, 2023 15:15
@LetItRock LetItRock added this pull request to the merge queue Mar 2, 2023
Merged via the queue into next with commit 144aa8c Mar 2, 2023
@LetItRock LetItRock deleted the nv-1753-dal-and-api-strict-null-check branch March 2, 2023 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants