1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

fix: linter rule for hooks (#9660)

This commit is contained in:
Tymoteusz Czech 2025-04-01 14:33:17 +02:00 committed by GitHub
parent a9490e6fe4
commit 6e947a8ba6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 100 additions and 123 deletions

View File

@ -11,7 +11,8 @@
"noUnsafeOptionalChaining": "off",
"useExhaustiveDependencies": "off",
"noUnusedImports": "warn",
"useJsxKeyInIterable": "off"
"useJsxKeyInIterable": "off",
"useHookAtTopLevel": "error"
},
"complexity": {
"noBannedTypes": "off",
@ -100,5 +101,17 @@
"formatter": {
"indentWidth": 2
}
}
},
"overrides": [
{
"include": ["src/**"],
"linter": {
"rules": {
"correctness": {
"useHookAtTopLevel": "off"
}
}
}
}
]
}

View File

@ -45,8 +45,7 @@ export const BillingDetailsPAYG = ({
const billableUsers = Math.max(eligibleUsers.length, minSeats);
const usersCost = seatPrice * billableUsers;
const includedTraffic = BILLING_INCLUDED_REQUESTS;
const overageCost = useOverageCost(includedTraffic);
const overageCost = useOverageCost(BILLING_INCLUDED_REQUESTS);
const totalCost = usersCost + overageCost;

View File

@ -52,8 +52,7 @@ export const BillingDetailsPro = ({
const freeAssigned = Math.min(eligibleUsers.length, seats);
const paidAssigned = eligibleUsers.length - freeAssigned;
const paidAssignedPrice = seatPrice * paidAssigned;
const includedTraffic = BILLING_INCLUDED_REQUESTS;
const overageCost = useOverageCost(includedTraffic);
const overageCost = useOverageCost(BILLING_INCLUDED_REQUESTS);
const totalCost = planPrice + paidAssignedPrice + overageCost;

View File

@ -9,10 +9,6 @@ import { BILLING_TRAFFIC_PRICE } from './BillingPlan';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useOverageCost = (includedTraffic: number) => {
if (!includedTraffic) {
return 0;
}
const now = new Date();
const formatDate = (date: Date) => format(date, 'yyyy-MM-dd');
const from = formatDate(startOfMonth(now));

View File

@ -12,7 +12,10 @@ import type {
} from '../../changeRequest.types';
import { HtmlTooltip } from '../../../common/HtmlTooltip/HtmlTooltip';
import ErrorIcon from '@mui/icons-material/Error';
import { useLocationSettings } from 'hooks/useLocationSettings';
import {
type ILocationSettings,
useLocationSettings,
} from 'hooks/useLocationSettings';
import { formatDateYMDHMS } from 'utils/formatDate';
export type ISuggestChangeTimelineProps =
@ -99,6 +102,7 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
data = steps;
}
const activeIndex = data.findIndex((item) => item === state);
const { locationSettings } = useLocationSettings();
return (
<StyledPaper elevation={0}>
@ -106,7 +110,10 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
<StyledTimeline>
{data.map((title, index) => {
if (schedule && title === 'Scheduled') {
return createTimelineScheduleItem(schedule);
return createTimelineScheduleItem(
schedule,
locationSettings,
);
}
const color = determineColor(
@ -195,9 +202,10 @@ export const getScheduleProps = (
}
};
const createTimelineScheduleItem = (schedule: ChangeRequestSchedule) => {
const { locationSettings } = useLocationSettings();
const createTimelineScheduleItem = (
schedule: ChangeRequestSchedule,
locationSettings: ILocationSettings,
) => {
const time = formatDateYMDHMS(
new Date(schedule.scheduledAt),
locationSettings?.locale,

View File

@ -169,9 +169,8 @@ export const RecentlyVisitedFeatureButton = ({
featureId: string;
onClick: () => void;
}) => {
const { trackEvent } = usePlausibleTracker();
const onItemClick = () => {
const { trackEvent } = usePlausibleTracker();
trackEvent('command-bar', {
props: {
eventType: `click`,

View File

@ -5,5 +5,6 @@ const SearchHighlightContext = createContext('');
export const SearchHighlightProvider = SearchHighlightContext.Provider;
export const useSearchHighlightContext = (): { searchQuery: string } => {
return { searchQuery: useContext(SearchHighlightContext) };
const searchQuery = useContext(SearchHighlightContext);
return { searchQuery };
};

View File

@ -1,11 +1,11 @@
import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { FeatureOverviewCell as makeFeatureOverviewCell } from './FeatureOverviewCell';
import { createFeatureOverviewCell } from './FeatureOverviewCell';
const noOp = () => {};
test('Display full overview information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp);
const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render(
<FeatureOverviewCell
@ -43,7 +43,7 @@ test('Display full overview information', () => {
});
test('Display minimal overview information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp);
const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render(
<FeatureOverviewCell
@ -69,7 +69,7 @@ test('Display minimal overview information', () => {
});
test('show archived information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp);
const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render(
<FeatureOverviewCell

View File

@ -436,7 +436,7 @@ const SecondaryFeatureInfo: FC<{
);
};
export const FeatureOverviewCell =
export const createFeatureOverviewCell =
(
onTagClick: (tag: string) => void,
onFlagTypeClick: (type: string) => void,

View File

@ -6,12 +6,7 @@ const allFilterKeys = ['from', 'to', 'createdBy', 'type', 'project', 'feature'];
allFilterKeys.sort();
test('When you have no projects or flags, you should not get a project or flag filters', () => {
const { result } = renderHook(() =>
useEventLogFilters(
() => ({ projects: [] }),
() => ({ features: [] }),
),
);
const { result } = renderHook(() => useEventLogFilters([], []));
const filterKeys = result.current.map((filter) => filter.filterKey);
filterKeys.sort();
@ -22,9 +17,9 @@ test('When you have no projects or flags, you should not get a project or flag f
test('When you have no projects, you should not get a project filter', () => {
const { result } = renderHook(() =>
useEventLogFilters(
() => ({ projects: [] }),
[],
// @ts-expect-error: omitting other properties we don't need
() => ({ features: [{ name: 'flag' }] }),
[{ name: 'flag' }],
),
);
const filterKeys = result.current.map((filter) => filter.filterKey);
@ -35,10 +30,7 @@ test('When you have no projects, you should not get a project filter', () => {
test('When you have only one project, you should not get a project filter', () => {
const { result } = renderHook(() =>
useEventLogFilters(
() => ({ projects: [{ id: 'a', name: 'A' }] }),
() => ({ features: [] }),
),
useEventLogFilters([{ id: 'a', name: 'A' }], []),
);
const filterKeys = result.current.map((filter) => filter.filterKey);
filterKeys.sort();
@ -49,13 +41,11 @@ test('When you have only one project, you should not get a project filter', () =
test('When you have two one project, you should not get a project filter', () => {
const { result } = renderHook(() =>
useEventLogFilters(
() => ({
projects: [
{ id: 'a', name: 'A' },
{ id: 'b', name: 'B' },
],
}),
() => ({ features: [] }),
[
{ id: 'a', name: 'A' },
{ id: 'b', name: 'B' },
],
[],
),
);
const filterKeys = result.current.map((filter) => filter.filterKey);

View File

@ -6,24 +6,15 @@ import {
} from 'component/filter/Filters/Filters';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch';
import {
EventSchemaType,
type FeatureSearchResponseSchema,
type SearchFeaturesParams,
} from 'openapi';
import { EventSchemaType, type FeatureSearchResponseSchema } from 'openapi';
import type { ProjectSchema } from 'openapi';
import { useEventCreators } from 'hooks/api/getters/useEventCreators/useEventCreators';
export const useEventLogFilters = (
projectsHook: () => { projects: ProjectSchema[] },
featuresHook: (params: SearchFeaturesParams) => {
features: FeatureSearchResponseSchema[];
},
projects: ProjectSchema[],
features: FeatureSearchResponseSchema[],
) => {
const { projects } = projectsHook();
const { features } = featuresHook({});
const { eventCreators } = useEventCreators();
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
useEffect(() => {
const projectOptions =
@ -124,22 +115,6 @@ export const useEventLogFilters = (
};
type LogType = 'flag' | 'project' | 'global';
const useEventLogFiltersFromLogType = (logType: LogType) => {
switch (logType) {
case 'flag':
return useEventLogFilters(
() => ({ projects: [] }),
() => ({ features: [] }),
);
case 'project':
return useEventLogFilters(
() => ({ projects: [] }),
useFeatureSearch,
);
case 'global':
return useEventLogFilters(useProjects, useFeatureSearch);
}
};
type EventLogFiltersProps = {
logType: LogType;
@ -154,7 +129,14 @@ export const EventLogFilters: FC<EventLogFiltersProps> = ({
state,
onChange,
}) => {
const availableFilters = useEventLogFiltersFromLogType(logType);
const { features } = useFeatureSearch({});
const { projects } = useProjects();
const featuresToFilter = logType !== 'flag' ? features : [];
const projectsToFilter = logType === 'global' ? projects : [];
const availableFilters = useEventLogFilters(
projectsToFilter,
featuresToFilter,
);
return (
<Filters

View File

@ -89,14 +89,14 @@ export const EnvironmentAccordionBody = ({
}
}, []);
if (!featureEnvironment) {
return null;
}
const pageSize = 20;
const { page, pages, setPageIndex, pageIndex } =
usePagination<IFeatureStrategy>(strategies, pageSize);
if (!featureEnvironment) {
return null;
}
const onReorder = async (payload: { id: string; sortOrder: number }[]) => {
try {
await setStrategiesSortOrder(

View File

@ -101,14 +101,14 @@ const EnvironmentAccordionBody = ({
}
}, []);
if (!featureEnvironment) {
return null;
}
const pageSize = 20;
const { page, pages, setPageIndex, pageIndex } =
usePagination<IFeatureStrategy>(strategies, pageSize);
if (!featureEnvironment) {
return null;
}
const onReorder = async (payload: { id: string; sortOrder: number }[]) => {
try {
await setStrategiesSortOrder(

View File

@ -11,7 +11,7 @@ import { TagTypeSelect } from './TagTypeSelect';
import { type TagOption, TagsInput } from './TagsInput';
import useTags from 'hooks/api/getters/useTags/useTags';
import useTagTypes from 'hooks/api/getters/useTagTypes/useTagTypes';
import type { ITag, ITagType } from 'interfaces/tags';
import type { ITagType } from 'interfaces/tags';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useTagApi from 'hooks/api/actions/useTagApi/useTagApi';
import type { TagSchema } from 'openapi';

View File

@ -35,7 +35,7 @@ import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
import { TableEmptyState } from './TableEmptyState/TableEmptyState';
import { useRowActions } from './hooks/useRowActions';
import { useSelectedData } from './hooks/useSelectedData';
import { FeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
import { createFeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
import {
useProjectFeatureSearch,
useProjectFeatureSearchActions,
@ -213,7 +213,7 @@ export const ProjectFeatureToggles = ({
columnHelper.accessor('name', {
id: 'name',
header: 'Name',
cell: FeatureOverviewCell(onTagClick, onFlagTypeClick),
cell: createFeatureOverviewCell(onTagClick, onFlagTypeClick),
enableHiding: false,
}),
columnHelper.accessor('createdAt', {

View File

@ -2,7 +2,6 @@ import { useMemo, useState, type VFC } from 'react';
import { Button } from '@mui/material';
import { ManageBulkTagsDialog } from 'component/feature/FeatureView/FeatureOverview/ManageTagsDialog/ManageBulkTagsDialog';
import type { FeatureSchema, TagSchema } from 'openapi';
import type { ITag } from 'interfaces/tags';
import useTagApi from 'hooks/api/actions/useTagApi/useTagApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';

View File

@ -38,10 +38,10 @@ interface IProjectStatsProps {
}
export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
const projectId = useRequiredPathParam('projectId');
if (Object.keys(stats).length === 0) {
return null;
}
const projectId = useRequiredPathParam('projectId');
const {
avgTimeToProdCurrentWindow,

View File

@ -1,4 +1,4 @@
import { useContext, useMemo, useState, type VFC } from 'react';
import { useContext, useMemo, useState, type VFC as FC } from 'react';
import { type HeaderGroup, useGlobalFilter, useTable } from 'react-table';
import { Alert, Box, styled, Typography } from '@mui/material';
import {
@ -41,8 +41,9 @@ const StyledBox = styled(Box)(({ theme }) => ({
},
}));
export const ChangeRequestTable: VFC = () => {
export const ChangeRequestTable: FC = () => {
const { trackEvent } = usePlausibleTracker();
const { hasAccess } = useContext(AccessContext);
const [dialogState, setDialogState] = useState<{
isOpen: boolean;
enableEnvironment: string;
@ -139,43 +140,33 @@ export const ChangeRequestTable: VFC = () => {
},
{
Header: 'Required approvals',
Cell: ({ row: { original } }: any) => {
const { hasAccess } = useContext(AccessContext);
return (
<ConditionallyRender
condition={original.changeRequestEnabled}
show={
<StyledBox data-loading>
<GeneralSelect
sx={{ width: '140px', marginLeft: 1 }}
options={approvalOptions}
value={original.requiredApprovals || 1}
onChange={(approvals) => {
onRequiredApprovalsChange(
original,
approvals,
);
}}
disabled={
!hasAccess(
[
UPDATE_PROJECT,
PROJECT_CHANGE_REQUEST_WRITE,
],
projectId,
)
}
IconComponent={
KeyboardArrowDownOutlined
}
fullWidth
/>
</StyledBox>
}
/>
);
},
Cell: ({ row: { original } }: any) =>
original.changeRequestEnabled ? (
<StyledBox data-loading>
<GeneralSelect
sx={{ width: '140px', marginLeft: 1 }}
options={approvalOptions}
value={original.requiredApprovals || 1}
onChange={(approvals) => {
onRequiredApprovalsChange(
original,
approvals,
);
}}
disabled={
!hasAccess(
[
UPDATE_PROJECT,
PROJECT_CHANGE_REQUEST_WRITE,
],
projectId,
)
}
IconComponent={KeyboardArrowDownOutlined}
fullWidth
/>
</StyledBox>
) : null,
width: 100,
disableGlobalFilter: true,
disableSortBy: true,