Migrate from ESLint to OxLint (#18443)
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension
This commit is contained in:
@@ -48,7 +48,7 @@ export const useAggregateRecords = <T extends AggregateRecordsData>({
|
||||
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
|
||||
aggregateQuery,
|
||||
{
|
||||
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
|
||||
variables: {
|
||||
filter,
|
||||
},
|
||||
|
||||
@@ -142,26 +142,30 @@ export const useCreateManyRecords = <
|
||||
}
|
||||
});
|
||||
|
||||
const recordsCreatedInCache = recordOptimisticRecordsInput
|
||||
.map((recordToCreate) =>
|
||||
createOneRecordInCache({
|
||||
const recordsCreatedInCache = recordOptimisticRecordsInput.flatMap(
|
||||
(recordToCreate) => {
|
||||
const created = createOneRecordInCache({
|
||||
...recordToCreate,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
});
|
||||
|
||||
return created !== undefined && created !== null ? [created] : [];
|
||||
},
|
||||
);
|
||||
|
||||
if (recordsCreatedInCache.length > 0) {
|
||||
const recordNodeCreatedInCache = recordsCreatedInCache
|
||||
.map((record) =>
|
||||
getRecordNodeFromRecord({
|
||||
const recordNodeCreatedInCache = recordsCreatedInCache.flatMap(
|
||||
(record) => {
|
||||
const node = getRecordNodeFromRecord({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
record: record,
|
||||
computeReferences: false,
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
});
|
||||
|
||||
return node !== undefined && node !== null ? [node] : [];
|
||||
},
|
||||
);
|
||||
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache: apolloCoreClient.cache,
|
||||
|
||||
@@ -162,7 +162,7 @@ export const useCreateOneRecord = <
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!recordCreatedInCache) {
|
||||
if (!isDefined(recordCreatedInCache)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
@@ -70,7 +71,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
const results = useMemo(
|
||||
() =>
|
||||
objectResults?.map((result: RecordGqlConnectionEdgesRequired) => {
|
||||
return result
|
||||
return isDefined(result)
|
||||
? (getRecordsFromRecordConnection({
|
||||
recordConnection: result,
|
||||
}) as T[])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useQuery, type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
@@ -89,7 +90,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
|
||||
const { data, loading, error, fetchMore, refetch } =
|
||||
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
|
||||
variables: {
|
||||
filter: withSoftDeleteFilter,
|
||||
orderBy,
|
||||
|
||||
@@ -56,7 +56,7 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
const { data, loading, error, refetch } = useQuery<{
|
||||
[nameSingular: string]: RecordGqlNode;
|
||||
}>(findOneRecordQuery, {
|
||||
skip: !objectMetadataItem || !objectRecordId || skip || !hasReadPermission,
|
||||
skip: !isDefined(objectMetadataItem) || !objectRecordId || skip || !hasReadPermission,
|
||||
variables: { objectRecordId },
|
||||
client: apolloCoreClient,
|
||||
onCompleted: (data) => {
|
||||
|
||||
+2
-2
@@ -102,12 +102,12 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
: null;
|
||||
|
||||
const relativeDate =
|
||||
resolvedValue && typeof resolvedValue === 'object'
|
||||
isDefined(resolvedValue) && typeof resolvedValue === 'object'
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
const safePlainDateValue: string | undefined =
|
||||
resolvedValue && typeof resolvedValue === 'string'
|
||||
isDefined(resolvedValue) && typeof resolvedValue === 'string'
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export const getRelativeDateDisplayValue = (
|
||||
relativeDate: RelativeDateFilter,
|
||||
shouldDisplayTimeZoneAbbreviation?: boolean,
|
||||
) => {
|
||||
if (!relativeDate) return '';
|
||||
if (!isDefined(relativeDate)) return '';
|
||||
const { direction, amount, unit } = relativeDate;
|
||||
|
||||
const directionFormatted = capitalize(direction.toLowerCase());
|
||||
|
||||
+2
-1
@@ -1,11 +1,12 @@
|
||||
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useObjectOptionsDropdown = () => {
|
||||
const context = useContext(ObjectOptionsDropdownContext);
|
||||
|
||||
if (!context) {
|
||||
if (!isDefined(context)) {
|
||||
throw new Error('useObjectOptionsDropdown must be used within a context');
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export const useObjectOptionsForBoard = ({
|
||||
recordIndexFieldDefinitionsByKey[fieldMetadataId];
|
||||
|
||||
return {
|
||||
...(existingBoardField || availableColumnDefinition),
|
||||
...(existingBoardField ?? availableColumnDefinition),
|
||||
isVisible: false,
|
||||
};
|
||||
}),
|
||||
|
||||
+2
-2
@@ -53,9 +53,9 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
<StyledDraggableContainer
|
||||
id={`record-board-card-${columnIndex}-${rowIndex}`}
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
data-selectable-id={recordId}
|
||||
data-select-disable
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ export const RecordBoardColumnCardsContainer = ({
|
||||
return (
|
||||
<StyledColumnCardsContainer
|
||||
ref={droppableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided?.droppableProps}
|
||||
>
|
||||
{recordIndexRecordIdsByGroup.map((recordId, index) => (
|
||||
@@ -68,7 +68,7 @@ export const RecordBoardColumnCardsContainer = ({
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
></div>
|
||||
)}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDrop
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
|
||||
|
||||
@@ -36,7 +37,7 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
||||
<>
|
||||
<StyledTagContainer>
|
||||
<Tag
|
||||
text={value ? value.toString() : '-'}
|
||||
text={isDefined(value) ? value.toString() : '-'}
|
||||
color="transparent"
|
||||
weight="regular"
|
||||
/>
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ export const RecordCalendarMonthBodyDay = ({
|
||||
<Droppable droppableId={dayKey}>
|
||||
{(droppableProvided, droppableSnapshot) => (
|
||||
<StyledCardsContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided.droppableProps}
|
||||
ref={droppableProvided.innerRef}
|
||||
isDraggedOver={droppableSnapshot.isDraggingOver}
|
||||
|
||||
+2
-2
@@ -77,9 +77,9 @@ export const RecordCalendarCardDraggableContainer = ({
|
||||
<StyledDraggableContainer
|
||||
id={`record-calendar-card-${recordId}`}
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
data-selectable-id={recordId}
|
||||
>
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { type DropResult } from '@hello-pangea/dnd';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop';
|
||||
|
||||
@@ -22,7 +23,7 @@ export const useProcessBoardCardDrop = () => {
|
||||
|
||||
const processBoardCardDrop = useCallback(
|
||||
(boardCardDropResult: DropResult, selectedRecordIds: string[]) => {
|
||||
if (!selectFieldMetadataItem) return;
|
||||
if (!isDefined(selectFieldMetadataItem)) return;
|
||||
|
||||
processGroupDrop({
|
||||
groupDropResult: boardCardDropResult,
|
||||
|
||||
+5
-1
@@ -40,7 +40,11 @@ export const RecordDetailDuplicatesSection = ({
|
||||
objectRecordIds: duplicateRecordIds,
|
||||
});
|
||||
|
||||
if (!queryResults || !queryResults[0] || queryResults[0].length === 0)
|
||||
if (
|
||||
!isDefined(queryResults) ||
|
||||
!isDefined(queryResults[0]) ||
|
||||
queryResults[0].length === 0
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ export const RecordDetailRelationSectionDropdownToMany = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById';
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { CustomError } from 'twenty-shared/utils';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
import { IconForbid, IconPencil } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
@@ -56,7 +56,7 @@ export const RecordDetailRelationSectionDropdownToOne = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
|
||||
import { recordStoreMorphOneToManyValueWithObjectNameFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { CustomError, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { CustomError, isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
|
||||
export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
|
||||
recordId,
|
||||
@@ -28,7 +28,7 @@ export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
|
||||
...recordWithObjectNameSingular,
|
||||
value: Array.isArray(recordWithObjectNameSingular.value)
|
||||
? recordWithObjectNameSingular.value
|
||||
: recordWithObjectNameSingular.value
|
||||
: isDefined(recordWithObjectNameSingular.value)
|
||||
? [recordWithObjectNameSingular.value]
|
||||
: [],
|
||||
}))
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import { type VariablePickerComponent } from '@/object-record/record-field/ui/fo
|
||||
import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type FormPhoneFieldInputProps = {
|
||||
label?: string;
|
||||
@@ -39,7 +40,7 @@ export const FormPhoneFieldInput = ({
|
||||
onChange({
|
||||
primaryPhoneCountryCode: defaultValue?.primaryPhoneCountryCode ?? '',
|
||||
primaryPhoneCallingCode: defaultValue?.primaryPhoneCallingCode ?? '',
|
||||
primaryPhoneNumber: number ? `${number}` : '',
|
||||
primaryPhoneNumber: isDefined(number) ? `${number}` : '',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -8,6 +8,7 @@ import { VariableChipStandalone } from '@/object-record/record-field/ui/form-typ
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||
import { styled } from '@linaria/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -43,7 +44,6 @@ export const FormSingleRecordFieldChip = ({
|
||||
disabled,
|
||||
}: FormSingleRecordFieldChipProps) => {
|
||||
if (
|
||||
!!draftValue &&
|
||||
draftValue.type === 'variable' &&
|
||||
isStandaloneVariableString(draftValue.value)
|
||||
) {
|
||||
@@ -56,7 +56,7 @@ export const FormSingleRecordFieldChip = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!!draftValue && draftValue.type === 'static' && !!selectedRecord) {
|
||||
if (draftValue.type === 'static' && isDefined(selectedRecord)) {
|
||||
return (
|
||||
<StyledRecordChipContainer>
|
||||
<RecordChip
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ export const FormSingleRecordPicker = ({
|
||||
}
|
||||
: {
|
||||
type: 'static',
|
||||
value: defaultValue || '',
|
||||
value: (defaultValue as string | undefined) ?? '',
|
||||
};
|
||||
|
||||
if (objectNameSingulars.length === 0) {
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { useMultiSelectFieldDisplay } from '@/object-record/record-field/ui/meta
|
||||
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const MultiSelectFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition } = useMultiSelectFieldDisplay();
|
||||
@@ -15,7 +16,7 @@ export const MultiSelectFieldDisplay = () => {
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!selectedOptions) return null;
|
||||
if (!isDefined(selectedOptions)) return null;
|
||||
|
||||
return isFocused ? (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ import {
|
||||
type InputHTMLAttributes,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
const StyledInput = styled.input<{
|
||||
@@ -159,13 +159,13 @@ export const MultiItemBaseInput = forwardRef<
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
ref={combinedRef}
|
||||
withRightComponent={!!rightComponent}
|
||||
withRightComponent={isDefined(rightComponent)}
|
||||
hasItem={hasItem}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
)}
|
||||
{!!rightComponent && (
|
||||
{isDefined(rightComponent) && (
|
||||
<StyledRightContainer>{rightComponent}</StyledRightContainer>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ export const RelationManyToOneFieldInput = () => {
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
objectMetadataItems,
|
||||
});
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ export const RelationOneToManyFieldInput = () => {
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
objectMetadataItems,
|
||||
});
|
||||
if (!fieldMetadataItem || !objectMetadataItem) {
|
||||
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
|
||||
throw new CustomError(
|
||||
'Field metadata item or object metadata item not found',
|
||||
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||
@@ -209,7 +209,7 @@ export const RelationOneToManyFieldInput = () => {
|
||||
const { targetFields, sourceField } = junctionConfig;
|
||||
const targetField = targetFields[0];
|
||||
|
||||
if (!targetField || !sourceField) {
|
||||
if (!isDefined(targetField) || !isDefined(sourceField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useCurrentRecordGroupId = (): string => {
|
||||
const context = useContext(RecordGroupContext);
|
||||
|
||||
if (!context) {
|
||||
if (!isDefined(context)) {
|
||||
throw new Error(
|
||||
'useCurrentRecordGroupId must be used within a RecordGroupContextProvider.',
|
||||
);
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export const RecordIndexPageHeaderIcon = ({
|
||||
const { getIcon } = useIcons();
|
||||
const ObjectIcon = getIcon(objectMetadataItem?.icon);
|
||||
|
||||
if (!ObjectIcon) {
|
||||
if (!isDefined(ObjectIcon)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -28,5 +28,5 @@ export const getPositionNumberIcon = (index: number) => {
|
||||
IconSquareNumber9,
|
||||
];
|
||||
|
||||
return iconMapping[index] || IconSquareNumber1;
|
||||
return iconMapping[index] ?? IconSquareNumber1;
|
||||
};
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledEditableTitleContainer = styled.div`
|
||||
@@ -92,7 +93,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
navigateToIndexView();
|
||||
}}
|
||||
>
|
||||
{HeaderIcon && <HeaderIcon size={theme.icon.size.md} />}
|
||||
{isDefined(HeaderIcon) && <HeaderIcon size={theme.icon.size.md} />}
|
||||
{objectLabel}
|
||||
<span>{' / '}</span>
|
||||
</StyledEditableTitlePrefix>
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ const RelationFieldValueSetterEffect = () => {
|
||||
mockPerformance.recordId,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const setRelationRecordStore = useSetAtomFamilyState(
|
||||
recordStoreFamilyState,
|
||||
mockPerformance.relationRecordId,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable twenty/no-navigate-prefer-link */
|
||||
/* oxlint-disable twenty/no-navigate-prefer-link */
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const RecordTableBodyNoRecordGroupDroppable = ({
|
||||
{(provided) => (
|
||||
<RecordTableBody
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export const RecordTableBodyRecordGroupDroppable = ({
|
||||
{(provided) => (
|
||||
<RecordTableBody
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ export const RecordTableBodyVirtualizedDraggableClone = ({
|
||||
recordId={recordId}
|
||||
focusIndex={realIndex}
|
||||
ref={draggableProvided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
style={{
|
||||
...draggableProvided.draggableProps.style,
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export const RecordTableCellDragAndDrop = () => {
|
||||
|
||||
return (
|
||||
<RecordTableCellStyleWrapper
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...dragHandleProps}
|
||||
data-select-disable
|
||||
hasRightBorder={false}
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ export const RecordTableCellFirstRowFirstColumn = ({
|
||||
hasRightBorder={hasRightBorder}
|
||||
hasBottomBorder={hasBottomBorder}
|
||||
zIndex={zIndex}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...dragHandleProps}
|
||||
className={cx(
|
||||
'table-cell-0-0',
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ export const RecordTableCellStyleWrapper = ({
|
||||
fontColor={fontColor}
|
||||
hasRightBorder={hasRightBorder}
|
||||
hasBottomBorder={hasBottomBorder}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...dragHandleProps}
|
||||
className={cx('table-cell', widthClassName)}
|
||||
>
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ export const RecordTableDraggableTr = ({
|
||||
focusIndex={focusIndex}
|
||||
ref={draggableProvided.innerRef}
|
||||
className={className}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
style={{
|
||||
...draggableProvided.draggableProps.style,
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ export const RecordTableDraggableTrFirstRowOfGroup = ({
|
||||
focusIndex={focusIndex}
|
||||
ref={draggableProvided.innerRef}
|
||||
className={className}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
style={{
|
||||
...draggableProvided.draggableProps.style,
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
import { RecordTableRowDiv } from '@/object-record/record-table/record-table-row/components/RecordTableRowDiv';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { isActive } from '@tiptap/core';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
|
||||
type RecordTableTrProps = {
|
||||
@@ -43,10 +42,10 @@ export const RecordTableFirstRowOfGroup = forwardRef<
|
||||
data-virtualized-id={recordId}
|
||||
isDragging={isDragging}
|
||||
ref={ref}
|
||||
data-active={isActive}
|
||||
data-focused={isRowFocusActive && isFocused && !isActive}
|
||||
data-active={false}
|
||||
data-focused={isRowFocusActive && isFocused}
|
||||
data-next-row-active-or-focused={isNextRowActiveOrFocused}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
isScrolledVertically={isRecordTableScrolledVertically}
|
||||
isFirstRowOfGroup={true}
|
||||
|
||||
+4
-4
@@ -50,7 +50,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
|
||||
focusIndex,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isNextRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
focusIndex + 1,
|
||||
@@ -75,7 +75,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isNextRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
focusIndex + 1,
|
||||
@@ -123,7 +123,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
|
||||
isFocused={isRecordTableRowFocused}
|
||||
isRowFocusActive={isRecordTableRowFocusActive}
|
||||
recordId={recordId}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -141,7 +141,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
|
||||
!isRecordTableRowActive
|
||||
}
|
||||
data-next-row-active-or-focused={isNextRowActiveOrFocused}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
+3
-2
@@ -93,8 +93,9 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
JSON.stringify(lastContextStoreVirtualizedVisibleRecordFields) !==
|
||||
JSON.stringify(visibleRecordFields)
|
||||
) {
|
||||
const lastFields = lastContextStoreVirtualizedVisibleRecordFields || [];
|
||||
const currentFields = visibleRecordFields || [];
|
||||
const lastFields =
|
||||
lastContextStoreVirtualizedVisibleRecordFields ?? [];
|
||||
const currentFields = visibleRecordFields ?? [];
|
||||
|
||||
setLastContextStoreVirtualizedVisibleRecordFields(visibleRecordFields);
|
||||
|
||||
|
||||
+6
-4
@@ -53,10 +53,12 @@ export const computeOptimisticRecordFromInput = ({
|
||||
objectMetadataItem.fields.find((field) => {
|
||||
if (!isFieldMorphRelation(field)) return false;
|
||||
|
||||
return getFieldMetadataFromGqlField({
|
||||
objectMetadataItem,
|
||||
gqlField: recordKey,
|
||||
});
|
||||
return isDefined(
|
||||
getFieldMetadataFromGqlField({
|
||||
objectMetadataItem,
|
||||
gqlField: recordKey,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const isUnknownField =
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ export const filterUniqueRecordEdgesByCursor = (
|
||||
|
||||
return seenCursors.has(currentCursor)
|
||||
? false
|
||||
: seenCursors.add(currentCursor);
|
||||
: Boolean(seenCursors.add(currentCursor));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ export const generateAggregateQuery = ({
|
||||
recordGqlFields: RecordGqlFields;
|
||||
}) => {
|
||||
const selectedFields = Object.entries(recordGqlFields)
|
||||
.filter(([_, shouldBeQueried]) => shouldBeQueried)
|
||||
.filter(([_, shouldBeQueried]) => Boolean(shouldBeQueried))
|
||||
.map(([fieldName]) => fieldName)
|
||||
.join('\n ');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user