Files
twenty/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx
T
Charles Bochet ef499b6d47 Re-enable disabled lint rules and right-size CI runners (#18461)
## Summary

- Re-enable one lint rule that was temporarily disabled during the
ESLint-to-Oxlint migration:
- **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578
violations auto-fixed across 390 files
- Document why **`typescript/consistent-type-imports`** cannot be
auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata`
for DI, so converting constructor parameter imports to `import type`
erases them at compile time and breaks dependency injection at runtime
- Right-size CI runners, reducing 8-core usage from 18 jobs to 3:

| Change | Jobs | Rationale |
|--------|------|-----------|
| **Keep 8-core** | `ci-merge-queue/e2e-test`,
`ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing
max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) |
| **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation,
test, integration-test), `ci-front/front-sb-test`,
`ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into
10-12 parallel instances, I/O-bound (DB/Redis), or moderate single
builds |
| **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight
(build + curl health check) |
| **Removed** | `ci-front/front-chromatic-deployment` | Dead code —
permanently disabled with `if: false` |

- Fix merge queue CI issues:
- **Concurrency**: Use `merge_group.base_ref` instead of unique merge
group ref so new queue entries cancel previous runs
- **Required status checks**: Add `merge_group` trigger to all 6
required CI workflows (front, server, shared, website, docker-compose,
sdk) with `changed-files-check` auto-skipped for merge_group events —
status check jobs auto-pass without re-running full CI
- **Build caching**: Add Nx build cache restore/save to E2E test job
with fallback to `main` branch cache for faster frontend and server
builds

## Test plan

- [ ] CI passes on this PR (verifies lint rule auto-fix works)
- [ ] Verify 4-core runner jobs complete within their 30-minute timeouts
- [ ] Verify merge queue status checks auto-pass (ci-front-status-check,
ci-server-status-check, etc.)
- [ ] Verify merge queue E2E concurrency cancels previous runs when a
new PR enters the queue
2026-03-06 13:33:02 +00:00

167 lines
5.4 KiB
TypeScript

import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId';
import { type Task } from '@/activities/types/Task';
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
import { FieldContextProvider } from '@/object-record/record-field/ui/components/FieldContextProvider';
import { useContext } from 'react';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { Checkbox, CheckboxShape } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useCompleteTask } from '@/activities/tasks/hooks/useCompleteTask';
const StyledTaskBody = styled.div`
color: ${themeCssVariables.font.color.tertiary};
display: flex;
max-width: calc(80% - ${themeCssVariables.spacing[2]});
overflow: hidden;
padding-bottom: 1px;
text-overflow: ellipsis;
`;
const StyledTaskTitle = styled.div<{
completed: boolean;
}>`
align-items: center;
color: ${themeCssVariables.font.color.primary};
font-weight: ${themeCssVariables.font.weight.medium};
overflow: hidden;
padding: 0 ${themeCssVariables.spacing[2]};
padding-bottom: 1px;
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledDueDate = styled.div<{
isPast: boolean;
}>`
align-items: center;
color: ${({ isPast }) =>
isPast
? themeCssVariables.font.color.danger
: themeCssVariables.font.color.secondary};
display: flex;
gap: ${themeCssVariables.spacing[1]};
padding-left: ${themeCssVariables.spacing[1]};
white-space: nowrap;
`;
const StyledRightSideContainer = styled.div`
align-items: center;
display: inline-flex;
max-width: 50%;
`;
const StyledActivityTargetsContainer = styled.div`
overflow: clip;
width: 100%;
`;
const StyledPlaceholder = styled.div`
color: ${themeCssVariables.font.color.light};
`;
const StyledLeftSideContainer = styled.div`
align-items: center;
display: inline-flex;
display: flex;
flex: 1;
overflow: hidden;
`;
const StyledCheckboxContainer = styled.div`
display: flex;
`;
export const TaskRow = ({ task }: { task: Task }) => {
const { theme } = useContext(ThemeContext);
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
const body = getActivitySummary(task?.bodyV2?.blocknote ?? null);
const { completeTask } = useCompleteTask(task);
const baseComponentInstanceId = `task-row-targets-${task.id}`;
const componentInstanceId = useActivityTargetsComponentInstanceId(
baseComponentInstanceId,
);
return (
<ActivityRow
onClick={() => {
openRecordInSidePanel({
recordId: task.id,
objectNameSingular: CoreObjectNameSingular.Task,
});
}}
>
<StyledLeftSideContainer>
<StyledCheckboxContainer
onClick={(e) => {
e.stopPropagation();
}}
>
<Checkbox
checked={task.status === 'DONE'}
shape={CheckboxShape.Rounded}
onCheckedChange={completeTask}
/>
</StyledCheckboxContainer>
<StyledTaskTitle completed={task.status === 'DONE'}>
{task.title || <StyledPlaceholder>{t`Task title`}</StyledPlaceholder>}
</StyledTaskTitle>
<StyledTaskBody>
<OverflowingTextWithTooltip text={body} />
</StyledTaskBody>
</StyledLeftSideContainer>
<StyledRightSideContainer>
{task.dueAt && (
<StyledDueDate
isPast={hasDatePassed(task.dueAt) && task.status === 'TODO'}
>
<IconCalendar size={theme.icon.size.md} />
{beautifyExactDate(task.dueAt)}
</StyledDueDate>
)}
{
<StyledActivityTargetsContainer>
<FieldContextProvider
objectNameSingular={CoreObjectNameSingular.Task}
objectRecordId={task.id}
fieldMetadataName="taskTargets"
fieldPosition={0}
>
<RecordFieldsScopeContextProvider
value={{
scopeInstanceId: task.id,
}}
>
<StopPropagationContainer>
<ActivityTargetsInlineCell
activityObjectNameSingular={CoreObjectNameSingular.Task}
activityRecordId={task.id}
showLabel={false}
maxWidth={200}
componentInstanceId={componentInstanceId}
/>
</StopPropagationContainer>
</RecordFieldsScopeContextProvider>
</FieldContextProvider>
</StyledActivityTargetsContainer>
}
</StyledRightSideContainer>
</ActivityRow>
);
};