From e3b3aac77c77f8d62e21e3b7b35f6a8c26bc22ae Mon Sep 17 00:00:00 2001 From: Maksim Sviridov Date: Tue, 26 Nov 2024 15:20:19 +0300 Subject: [PATCH] fix(INTERNAL-1384): linking jira tasks --- generated/kysely/types.ts | 6 ++-- .../migration.sql | 4 +++ prisma/schema.prisma | 6 ++-- src/components/CriteriaForm/CriteriaForm.tsx | 19 +++++++++-- .../GoalActivityFeed/GoalActivityFeed.tsx | 12 +++---- .../GoalCriteria/GoalCriteria.module.css | 1 + src/components/GoalCriteria/GoalCriteria.tsx | 13 +++++--- src/components/GoalCriteriaSuggest.tsx | 3 +- .../HistoryRecord/HistoryRecord.tsx | 1 + .../JiraTaskBadge/JiraTaskBadge.module.css | 5 --- .../JiraTaskBadge/JiraTaskBadge.tsx | 18 +++++------ src/schema/criteria.ts | 2 +- src/utils/integration/jira.ts | 4 +-- src/utils/worker/externalTasksJob.ts | 12 +++---- trpc/queries/external.ts | 16 ++++++---- trpc/router/goal.ts | 32 ++++++++++--------- trpc/router/jira.ts | 17 ++++++---- 17 files changed, 98 insertions(+), 73 deletions(-) create mode 100644 prisma/migrations/20241126101153_optional_owner_fields/migration.sql diff --git a/generated/kysely/types.ts b/generated/kysely/types.ts index 6cfed0f98..3d49eea95 100644 --- a/generated/kysely/types.ts +++ b/generated/kysely/types.ts @@ -92,9 +92,9 @@ export type ExternalTask = { stateColor: string | null; stateCategoryId: number; stateCategoryName: string; - ownerEmail: string; - ownerName: string; - ownerId: string; + ownerEmail: string | null; + ownerName: string | null; + ownerId: string | null; creatorEmail: string | null; creatorName: string | null; creatorId: string | null; diff --git a/prisma/migrations/20241126101153_optional_owner_fields/migration.sql b/prisma/migrations/20241126101153_optional_owner_fields/migration.sql new file mode 100644 index 000000000..90c3d91af --- /dev/null +++ b/prisma/migrations/20241126101153_optional_owner_fields/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "ExternalTask" ALTER COLUMN "ownerEmail" DROP NOT NULL, +ALTER COLUMN "ownerName" DROP NOT NULL, +ALTER COLUMN "ownerId" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ba71313f0..09e8f0d13 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -442,9 +442,9 @@ model ExternalTask { stateColor String? stateCategoryId Int stateCategoryName String - ownerEmail String - ownerName String - ownerId String + ownerEmail String? + ownerName String? + ownerId String? creatorEmail String? creatorName String? creatorId String? diff --git a/src/components/CriteriaForm/CriteriaForm.tsx b/src/components/CriteriaForm/CriteriaForm.tsx index 23f4f6cd1..ce26d3d13 100644 --- a/src/components/CriteriaForm/CriteriaForm.tsx +++ b/src/components/CriteriaForm/CriteriaForm.tsx @@ -40,7 +40,7 @@ type SuggestItem = id: string; title: string; type: TaskTypeProps; - key: string; + taskKey: string; }; interface ValidityData { @@ -102,7 +102,7 @@ function patchZodSchema( mode: z.literal('task'), id: z.string(), selected: z.object({ - key: z.string(), + taskKey: z.string(), }), }), ]) @@ -414,6 +414,14 @@ export const CriteriaForm = ({ return !!(title && selected?.id); }, [mode, title, selected?.id]); + const disbaleSubmitBtn = useMemo(() => { + if (mode === 'simple') { + return !title.length; + } + + return selected == null || selected.title?.length === 0; + }, [mode, title, selected]); + const resetHandler = useCallback(() => { if (!isEditMode) { setShowWeightInput(false); @@ -513,7 +521,12 @@ export const CriteriaForm = ({
diff --git a/src/components/GoalActivityFeed/GoalActivityFeed.tsx b/src/components/GoalActivityFeed/GoalActivityFeed.tsx index 22992d23a..c9f5d7ed4 100644 --- a/src/components/GoalActivityFeed/GoalActivityFeed.tsx +++ b/src/components/GoalActivityFeed/GoalActivityFeed.tsx @@ -90,7 +90,7 @@ export const GoalActivityFeed = forwardRef { await onGoalCriteriaAdd({ @@ -104,9 +104,9 @@ export const GoalActivityFeed = forwardRef { if (!data.id) return; @@ -136,9 +136,9 @@ export const GoalActivityFeed = forwardRef & OnCheckCriteriaCallba onCheck, }) => ( - + - + - + | null; state?: Record | null; project: string; @@ -115,7 +116,7 @@ export const GoalCriteriaSuggest: React.FC = ({ return issues.map((issue) => ({ ...issue, - key: issue.externalKey, + taskKey: issue.externalKey, state: null, id: issue.externalId, title: issue.title, diff --git a/src/components/HistoryRecord/HistoryRecord.tsx b/src/components/HistoryRecord/HistoryRecord.tsx index 035e88966..3b49921e1 100644 --- a/src/components/HistoryRecord/HistoryRecord.tsx +++ b/src/components/HistoryRecord/HistoryRecord.tsx @@ -435,6 +435,7 @@ const HistoryRecordCriteriaItem: React.FC = ({ criteriaGoal, exter title: externalTask.state, color: externalTask.stateColor, }} + taskKey={externalTask.externalKey} title={externalTask.title} href={routes.jiraTask(externalTask.externalKey)} strike={strike} diff --git a/src/components/JiraTaskBadge/JiraTaskBadge.module.css b/src/components/JiraTaskBadge/JiraTaskBadge.module.css index ded009049..546473be3 100644 --- a/src/components/JiraTaskBadge/JiraTaskBadge.module.css +++ b/src/components/JiraTaskBadge/JiraTaskBadge.module.css @@ -3,11 +3,6 @@ font-size: 16px; } -.JiraTaskBadgeProjectName { - /* must be used of current font-size, but not root font-size */ - margin-left: 0.25em; -} - .JiraTaskBadgeState { color: white; background-color: var(--task-badge-color, var(--gray-500)); diff --git a/src/components/JiraTaskBadge/JiraTaskBadge.tsx b/src/components/JiraTaskBadge/JiraTaskBadge.tsx index 6ec476d01..f93d8f0a1 100644 --- a/src/components/JiraTaskBadge/JiraTaskBadge.tsx +++ b/src/components/JiraTaskBadge/JiraTaskBadge.tsx @@ -8,6 +8,7 @@ import { NextLink } from '../NextLink'; import styles from './JiraTaskBadge.module.css'; interface JiraTaskBadgeProps extends Omit, 'color' | 'title'> { + taskKey: string; title: React.ReactNode; href?: string; type?: { @@ -55,15 +56,14 @@ export const JiraTaskBadgeState: React.FC<{ state: string; color?: string | null ); }; -const JiraTaskBadgeLabel: React.FC> = ({ title, project }) => { +const JiraTaskBadgeLabel: React.FC> = ({ title, taskKey }) => { return ( <> {title} - {nullable(project, (p) => ( - - ({p}) - - ))} + {String.fromCharCode(0x0d)} + + ({taskKey}) + ); }; @@ -76,7 +76,7 @@ export const JiraTaskBadge: React.FC = ({ onClick, type, state, - project, + taskKey, ...attrs }) => { return ( @@ -94,10 +94,10 @@ export const JiraTaskBadge: React.FC = ({ href, (h) => ( - + ), - , + , )} action="dynamic" {...attrs} diff --git a/src/schema/criteria.ts b/src/schema/criteria.ts index 7ec3ae349..c0c0a650d 100644 --- a/src/schema/criteria.ts +++ b/src/schema/criteria.ts @@ -23,7 +23,7 @@ export const criteriaSchema = z.object({ .optional(), externalTask: z .object({ - externalKey: z.string(), + taskKey: z.string(), }) .optional(), }); diff --git a/src/utils/integration/jira.ts b/src/utils/integration/jira.ts index f6af654e2..cfaeafc21 100644 --- a/src/utils/integration/jira.ts +++ b/src/utils/integration/jira.ts @@ -77,9 +77,9 @@ export interface JiraIssue { self: string; summary: string; description: string; - creator: JiraUser; + creator: JiraUser | null; assignee: JiraUser | null; - reporter: JiraUser; + reporter: JiraUser | null; status: JiraIssueStatus; project: JiraProject; priority: JiraProject; diff --git a/src/utils/worker/externalTasksJob.ts b/src/utils/worker/externalTasksJob.ts index 73e155101..7ba9d456a 100644 --- a/src/utils/worker/externalTasksJob.ts +++ b/src/utils/worker/externalTasksJob.ts @@ -30,12 +30,12 @@ const createSQLValues = (sources: JiraIssue[]) => issuetype.name, issuetype.iconUrl, issuetype.id, - reporter.emailAddress, - reporter.key, - reporter.displayName || reporter.name || null, - creator.emailAddress, - creator.key, - creator.displayName || creator.name || null, + reporter?.emailAddress, + reporter?.key, + reporter?.displayName || reporter?.name || null, + creator?.emailAddress, + creator?.key, + creator?.displayName || creator?.name || null, assignee?.emailAddress, assignee?.key, assignee?.displayName || assignee?.name || null, diff --git a/trpc/queries/external.ts b/trpc/queries/external.ts index e8fe61e2c..a5211de77 100644 --- a/trpc/queries/external.ts +++ b/trpc/queries/external.ts @@ -1,4 +1,4 @@ -import { searchIssue } from '../../src/utils/integration/jira'; +import { JiraUser, searchIssue } from '../../src/utils/integration/jira'; import { db } from '../connection/kysely'; import { ExternalTask } from '../../generated/kysely/types'; import { ExtractTypeFromGenerated } from '../utils'; @@ -43,6 +43,8 @@ export const getOrCreateExternalTask = async ({ id }: { id: string }) => { resolution, } = externalIssue; + const creatorOrReporter = [reporter, creator].find((val) => val != null) || ({} as JiraUser); + return insertExternalTask({ title, externalId, @@ -58,12 +60,12 @@ export const getOrCreateExternalTask = async ({ id }: { id: string }) => { stateCategoryName: state.statusCategory.name, project: project.name, projectId: project.key, - ownerName: reporter.displayName, - ownerEmail: reporter.emailAddress, - ownerId: reporter.key, - creatorName: creator.displayName, - creatorEmail: creator.emailAddress, - creatorId: creator.key, + ownerName: creatorOrReporter.displayName ?? creatorOrReporter.name, + ownerEmail: creatorOrReporter.emailAddress, + ownerId: creatorOrReporter.key, + creatorName: creatorOrReporter.displayName ?? creatorOrReporter.name, + creatorEmail: creatorOrReporter.emailAddress, + creatorId: creatorOrReporter.key, assigneeName: assignee?.displayName ?? null, assigneeEmail: assignee?.emailAddress ?? null, assigneeId: assignee?.key ?? null, diff --git a/trpc/router/goal.ts b/trpc/router/goal.ts index 3ee62ce17..36e24a5bd 100644 --- a/trpc/router/goal.ts +++ b/trpc/router/goal.ts @@ -65,7 +65,7 @@ import { ReactionsMap } from '../../src/types/reactions'; import { safeGetUserName } from '../../src/utils/getUserName'; import { extraDataForEachRecord, goalHistorySeparator, historyQuery } from '../queries/history'; import { getOrCreateExternalTask, updateExternalTask } from '../queries/external'; -import { jiraService, searchIssue } from '../../src/utils/integration/jira'; +import { jiraService, JiraUser, searchIssue } from '../../src/utils/integration/jira'; import { tr } from './router.i18n'; @@ -1112,9 +1112,9 @@ export const goal = router({ // replace by default if have connected goal criteriaTitle = connectedGoal.title; } - } else if (input.externalTask?.externalKey) { + } else if (input.externalTask?.taskKey) { externalTask = await getOrCreateExternalTask({ - id: input.externalTask.externalKey, + id: input.externalTask.taskKey, }); if (externalTask) { @@ -1159,7 +1159,7 @@ export const goal = router({ } : undefined, externalTask: - input.externalTask?.externalKey && externalTask != null + input.externalTask?.taskKey && externalTask != null ? { connect: { id: externalTask.id, @@ -1248,15 +1248,15 @@ export const goal = router({ } } - if (input.externalTask?.externalKey) { - const existTask = await getOrCreateExternalTask({ id: input.externalTask?.externalKey }); + if (input.externalTask?.taskKey) { + const existTask = await getOrCreateExternalTask({ id: input.externalTask?.taskKey }); if (existTask) { // replace by default if have connected goal criteriaTitle = existTask.title; // override from db data for next connect between criteria and task - input.externalTask.externalKey = existTask.id; + input.externalTask.taskKey = existTask.id; isDoneByConnect = jiraService.checkStatusIsFinished( (await searchIssue({ value: existTask.externalKey, limit: 1 }))[0].status, @@ -1283,9 +1283,9 @@ export const goal = router({ connect: { id: input.criteriaGoal.id }, } : undefined, - externalTask: input.externalTask?.externalKey + externalTask: input.externalTask?.taskKey ? { - connect: { id: input.externalTask.externalKey }, + connect: { id: input.externalTask.taskKey }, } : undefined, createdAt: currentCriteria.createdAt, @@ -1493,6 +1493,8 @@ export const goal = router({ resolution, } = updatedTask; + const creatorOrReporter = [reporter, creator].find((val) => val != null) || ({} as JiraUser); + await updateExternalTask({ id: currentCriteria.externalTask.id, title, @@ -1509,12 +1511,12 @@ export const goal = router({ stateCategoryName: state.statusCategory.name, project: project.name, projectId: project.key, - ownerName: reporter.displayName, - ownerEmail: reporter.emailAddress, - ownerId: reporter.key, - creatorName: creator.displayName, - creatorEmail: creator.emailAddress, - creatorId: creator.key, + ownerName: creatorOrReporter.displayName ?? creatorOrReporter.name, + ownerEmail: creatorOrReporter.emailAddress, + ownerId: creatorOrReporter.key, + creatorName: creatorOrReporter.displayName ?? creatorOrReporter.name, + creatorEmail: creatorOrReporter.emailAddress, + creatorId: creatorOrReporter.key, assigneeName: assignee?.displayName ?? null, assigneeEmail: assignee?.emailAddress ?? null, assigneeId: assignee?.key ?? null, diff --git a/trpc/router/jira.ts b/trpc/router/jira.ts index f69973950..4ef939f8e 100644 --- a/trpc/router/jira.ts +++ b/trpc/router/jira.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { protectedProcedure, router } from '../trpcBackend'; -import { JiraIssue, searchIssue, jiraService } from '../../src/utils/integration/jira'; +import { JiraIssue, searchIssue, jiraService, JiraUser } from '../../src/utils/integration/jira'; import { ExternalTask } from '../../generated/kysely/types'; import { ExtractTypeFromGenerated } from '../utils'; @@ -9,6 +9,9 @@ const jiraIssueToExternalTask = ( issue: JiraIssue, ): Omit, 'createdAt' | 'id' | 'updatedAt'> => { const { key, id, summary, status, project, creator, reporter, assignee, resolution, issuetype } = issue; + + const creatorOrReporter = [reporter, creator].find((val) => val != null) || ({} as JiraUser); + return { externalKey: key, externalId: id, @@ -24,12 +27,12 @@ const jiraIssueToExternalTask = ( type: issuetype.name, typeIconUrl: issuetype.iconUrl, typeId: issuetype.id, - ownerEmail: reporter.emailAddress, - ownerId: reporter.key, - ownerName: reporter.displayName || reporter.name, - creatorEmail: creator.emailAddress, - creatorId: creator.key, - creatorName: creator.displayName || creator.name, + ownerEmail: creatorOrReporter?.emailAddress ?? null, + ownerId: creatorOrReporter?.key ?? null, + ownerName: creatorOrReporter?.displayName ?? creatorOrReporter?.name ?? null, + creatorEmail: creatorOrReporter?.emailAddress ?? null, + creatorId: creatorOrReporter?.key ?? null, + creatorName: creatorOrReporter?.displayName ?? creatorOrReporter?.name ?? null, assigneeEmail: assignee?.emailAddress ?? null, assigneeId: assignee?.key ?? null, assigneeName: assignee?.displayName ?? assignee?.name ?? null,