Skip to content

Commit

Permalink
fix(GoalHistory): rewrite types with calculated fields
Browse files Browse the repository at this point in the history
  • Loading branch information
LamaEats committed Dec 13, 2023
1 parent cacb2a5 commit 79c613a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 136 deletions.
59 changes: 13 additions & 46 deletions src/components/GoalActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { forwardRef } from 'react';
import { nullable } from '@taskany/bricks';

import { GoalByIdReturnType } from '../../trpc/inferredTypes';
import { HistoryAction } from '../types/history';
import { GoalComment } from '../types/comment';

import { ActivityFeed } from './ActivityFeed';
Expand All @@ -29,10 +28,6 @@ interface GoalActivityProps {
renderCommentItem: (item: GoalComment) => React.ReactNode;
}

function excludeString<T>(val: T): Exclude<T, string> {
return val as Exclude<T, string>;
}

export const GoalActivity = forwardRef<HTMLDivElement, GoalActivityProps>(
({ feed, header, footer, renderCommentItem }, ref) => {
return (
Expand All @@ -48,28 +43,18 @@ export const GoalActivity = forwardRef<HTMLDivElement, GoalActivityProps>(
<HistoryRecord
author={value.activity.user}
id={value.id}
subject={excludeString(value.subject)}
action={excludeString(value.action)}
subject={value.subject}
action={value.action}
createdAt={value.createdAt}
>
{value.subject === 'dependencies' && (
<HistoryRecordDependency
issues={
excludeString(value.previousValue || value.nextValue)?.map((val) => {
return {
...val,
_shortId: `${val.projectId}-${val.scopeId}`,
};
}, []) || []
}
issues={value.previousValue || value.nextValue}
strike={!!value.previousValue}
/>
)}
{value.subject === 'tags' && (
<HistoryRecordTags
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordTags from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'description' && (
<HistoryRecordLongTextChange from={value.previousValue} to={value.nextValue} />
Expand All @@ -78,46 +63,28 @@ export const GoalActivity = forwardRef<HTMLDivElement, GoalActivityProps>(
<HistoryRecordTextChange from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'estimate' && (
<HistoryRecordEstimate
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordEstimate from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'priority' && (
<HistoryRecordPriority
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordPriority from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'state' && (
<HistoryRecordState
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordState from={value.previousValue} to={value.nextValue} />
)}
{(value.subject === 'participants' || value.subject === 'owner') && (
<HistoryRecordParticipant
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordParticipant from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'project' && (
<HistoryRecordProject
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordProject from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'partnerProject' && (
<HistoryRecordPartnerProject
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
/>
<HistoryRecordPartnerProject from={value.previousValue} to={value.nextValue} />
)}
{value.subject === 'criteria' && (
<HistoryRecordCriteria
from={excludeString(value.previousValue)}
to={excludeString(value.nextValue)}
action={value.action as HistoryAction}
from={value.previousValue}
to={value.nextValue}
action={value.action}
/>
)}
</HistoryRecord>
Expand Down
14 changes: 7 additions & 7 deletions src/components/HistoryRecord/HistoryRecord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { RelativeTime } from '../RelativeTime/RelativeTime';
import { decodeHistoryEstimate, formateEstimate } from '../../utils/dateTime';
import { getPriorityText } from '../PriorityText/PriorityText';
import { StateDot } from '../StateDot';
import { HistoryAction, HistoryRecordSubject } from '../../types/history';
import { HistoryRecordAction, HistoryRecordSubject } from '../../types/history';
import { calculateDiffBetweenArrays } from '../../utils/calculateDiffBetweenArrays';
import { Circle } from '../Circle';
import { useLocale } from '../../hooks/useLocale';
Expand All @@ -44,7 +44,7 @@ interface HistoryRecordProps {
id: string;
author: User | null;
subject: WholeSubject;
action: HistoryAction;
action: HistoryRecordAction;
children?: React.ReactNode;
createdAt: Date;
}
Expand Down Expand Up @@ -113,7 +113,7 @@ const StyledFlexReset = styled.div`
`;

interface HistoryRecordContext {
setActionText: (value: SetStateAction<HistoryAction>) => void;
setActionText: (value: SetStateAction<HistoryRecordAction>) => void;
setSubjectText: (value: SetStateAction<WholeSubject>) => void;
}

Expand Down Expand Up @@ -177,7 +177,7 @@ export const HistoryMultilineRecord: React.FC<{ withPretext?: boolean } & Histor
);

export const HistoryRecord: React.FC<HistoryRecordProps> = ({ author, subject, action, createdAt, children }) => {
const translates = useMemo<Record<HistoryAction | WholeSubject, string>>(() => {
const translates = useMemo<Record<HistoryRecordAction | WholeSubject, string>>(() => {
return {
change: tr('change'),
edit: tr('edit'),
Expand Down Expand Up @@ -242,9 +242,9 @@ export const HistoryRecord: React.FC<HistoryRecordProps> = ({ author, subject, a
};

export const HistoryRecordDependency: React.FC<{
issues: Array<React.ComponentProps<typeof IssueListItem>['issue']>;
issues?: Array<React.ComponentProps<typeof IssueListItem>['issue']>;
strike?: boolean;
}> = ({ issues, strike = false }) => {
}> = ({ issues = [], strike = false }) => {
return (
<>
{issues.map((issue) => (
Expand Down Expand Up @@ -483,7 +483,7 @@ const HistoryRecordCriteriaItem: React.FC<CriteriaItem> = ({ criteriaGoal, title

export const HistoryRecordCriteria: React.FC<
HistoryChangeProps<CriteriaItem> & {
action: HistoryAction;
action: HistoryRecordAction;
strike?: boolean;
}
> = ({ from, to, action }) => {
Expand Down
54 changes: 29 additions & 25 deletions src/types/history.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import {
Goal,
State,
Project,
Tag,
Activity,
GoalHistory,
User,
GoalAchieveCriteria,
Ghost,
Priority,
} from '@prisma/client';
import { Goal, State, Project, Tag, Activity, User, GoalAchieveCriteria, Ghost, Priority } from '@prisma/client';

import { addCommonCalculatedGoalFields } from '../../trpc/queries/goals';

export const subjectToTableNameMap = {
dependencies: true,
Expand All @@ -23,10 +14,15 @@ export const subjectToTableNameMap = {
priority: true,
};

export type HistoryRecordSubject = { [K in keyof typeof subjectToTableNameMap]: string };
export type HistoryRecordSubject = { [K in keyof typeof subjectToTableNameMap]: string } & {
title: string;
description: string;
estimate: string;
};
export type HistoryRecordAction = 'add' | 'change' | 'remove' | 'delete' | 'edit' | 'complete' | 'uncomplete';

export interface HistoryRecordMeta {
dependencies: Goal & { state: State | null };
dependencies: Goal & { state: State | null } & ReturnType<typeof addCommonCalculatedGoalFields>;
project: Project;
tags: Tag;
owner: Activity & { user: User | null; ghost: Ghost | null };
Expand All @@ -35,21 +31,21 @@ export interface HistoryRecordMeta {
criteria: GoalAchieveCriteria & { criteriaGoal: Goal & { state: State | null } };
partnerProject: Project;
priority: Priority;
title: string;
description: string;
estimate: string;
}

export type HistoryAction = 'add' | 'change' | 'remove' | 'delete' | 'edit' | 'complete' | 'uncomplete';

type HistoryValuesBySubject<T extends keyof HistoryRecordSubject, V = HistoryRecordMeta[T]> = {
subject: T;
previousValue?: V;
nextValue?: V;
};

type HistoryRecord =
| GoalHistory
| ({
| {
id: string;
action: HistoryAction;
action: HistoryRecordAction;
createdAt: Date;
} & (
| HistoryValuesBySubject<'dependencies', HistoryRecordMeta['dependencies'][]>
Expand All @@ -60,12 +56,20 @@ type HistoryRecord =
| HistoryValuesBySubject<'owner'>
| HistoryValuesBySubject<'criteria'>
| HistoryValuesBySubject<'priority'>
| {
subject: 'title' | 'description' | 'estimate';
previousValue?: string;
nextValue?: string;
}
| HistoryValuesBySubject<'title' | 'description' | 'estimate', string | null>
| HistoryValuesBySubject<'partnerProject'>
));
);

export type HistoryRecordWithActivity = HistoryRecord & { activity: Activity & { user: User | null } };

export const castToSubject = (value: unknown): value is HistoryRecordWithActivity => {
if (value != null && typeof value === 'object') {
if ('subject' in value && value.subject != null && typeof value.subject === 'string') {
return (
value.subject in subjectToTableNameMap || ['title', 'description', 'estimate'].includes(value.subject)
);
}
}

return false;
};
88 changes: 39 additions & 49 deletions src/utils/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { GoalHistory, Comment, Activity, User, Goal, Role, Prisma, Reaction, Sta
import { TRPCError } from '@trpc/server';

import { GoalCommon, dependencyKind, exceptionsDependencyKind } from '../schema/goal';
import { addCalculatedGoalsFields } from '../../trpc/queries/goals';
import { HistoryRecordWithActivity, HistoryRecordSubject, HistoryAction } from '../types/history';
import { addCalculatedGoalsFields, addCommonCalculatedGoalFields } from '../../trpc/queries/goals';
import { HistoryRecordWithActivity, HistoryRecordSubject, castToSubject } from '../types/history';
import { ReactionsMap } from '../types/reactions';

import { prisma } from './prisma';
Expand Down Expand Up @@ -112,7 +112,7 @@ export const getGoalHistory = async <T extends GoalHistory & { activity: Activit
{},
);

const historyWithMeta: HistoryRecordWithActivity[] = Array.from(history);
const historyWithMeta: HistoryRecordWithActivity[] = [];
const needRequestForSubject = Object.keys(requestParamsBySubjects) as (keyof typeof requestParamsBySubjects)[];
const replacedValueIdx = needRequestForSubject
.map((subject) => (requestParamsBySubjects[subject] || {}).sourceIdx)
Expand All @@ -133,12 +133,14 @@ export const getGoalHistory = async <T extends GoalHistory & { activity: Activit

switch (subject) {
case 'dependencies':
return prisma.goal.findMany({
where: query.where,
include: {
state: true,
},
});
return prisma.goal
.findMany({
where: query.where,
include: {
state: true,
},
})
.then((deps) => deps.map((dep) => ({ ...dep, ...addCommonCalculatedGoalFields(dep) })));
case 'tags':
return prisma.tag.findMany(query);
case 'owner':
Expand Down Expand Up @@ -175,68 +177,56 @@ export const getGoalHistory = async <T extends GoalHistory & { activity: Activit
}),
);

const metaResults: Record<string, (typeof results)[number][number]>[] = [];

for (const records of results) {
const meta: Record<string, (typeof records)[number]> = {};

for (const record of records) {
meta[record.id] = record;
}

metaResults.push(meta);
}

const metaObj = metaResults.reduce((acc, values) => {
acc = { ...acc, ...values };

return acc;
}, {});

replacedValueIdx.forEach((sourceIndex) => {
if (sourceIndex == null) {
return;
}
const meta: Map<string, (typeof results)[number][number]> = new Map(
results.flat().map((value) => [value.id, value]),
);

const record = history[sourceIndex];
const valueCanBeArray = ['dependencies', 'tags', 'estimates'].includes(record.subject);
history.forEach((item, index) => {
const valueCanBeArray = ['dependencies', 'tags', 'estimates'].includes(item.subject);
let prev;
let next;

if (valueCanBeArray) {
historyWithMeta[sourceIndex] = {
...record,
action: record.action as HistoryAction,
previousValue: record.previousValue?.split(', ').map((id) => metaObj[id]),
nextValue: record.nextValue?.split(', ').map((id) => metaObj[id]),
};
prev = item.previousValue?.split(goalHistorySeparator).map((id) => meta.get(id)) ?? null;
next = item.nextValue?.split(goalHistorySeparator).map((id) => meta.get(id)) ?? null;
} else {
historyWithMeta[sourceIndex] = {
...record,
action: record.action as HistoryAction,
previousValue: record.previousValue ? metaObj[record.previousValue] : null,
nextValue: record.nextValue ? metaObj[record.nextValue] : null,
};
prev = item.previousValue ? meta.get(item.previousValue) : null;
next = item.nextValue ? meta.get(item.nextValue) : null;
}

if (castToSubject(item)) {
if (replacedValueIdx.includes(index)) {
historyWithMeta[index] = {
...item,
previousValue: prev || null,
nextValue: next || null,
};
} else {
historyWithMeta[index] = item;
}
}
});
}

return historyWithMeta;
};

type mixHistoryWithCommentsReturnType<H, C> =
type MixHistoryWithCommentsReturnType<H, C> =
| { type: 'history'; value: H }
| { type: 'comment'; value: Omit<C, 'reactions'> & { reactions: ReactionsMap } };

export const mixHistoryWithComments = <
H extends HistoryRecordWithActivity,
C extends Comment & { reactions: (Reaction & Pick<HistoryRecordWithActivity, 'activity'>)[] },
>(
history: H[],
comments: C[],
): {
_activityFeed: mixHistoryWithCommentsReturnType<H, C>[];
_comments: Extract<mixHistoryWithCommentsReturnType<H, C>, { type: 'comment' }>['value'][];
_activityFeed: MixHistoryWithCommentsReturnType<H, C>[];
_comments: Extract<MixHistoryWithCommentsReturnType<H, C>, { type: 'comment' }>['value'][];
comments: C[];
} => {
const activity: mixHistoryWithCommentsReturnType<H, C>[] = history.map((record) => {
const activity: MixHistoryWithCommentsReturnType<H, C>[] = history.map((record) => {
return {
type: 'history',
value: record,
Expand Down
Loading

0 comments on commit 79c613a

Please sign in to comment.