1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +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", "noUnsafeOptionalChaining": "off",
"useExhaustiveDependencies": "off", "useExhaustiveDependencies": "off",
"noUnusedImports": "warn", "noUnusedImports": "warn",
"useJsxKeyInIterable": "off" "useJsxKeyInIterable": "off",
"useHookAtTopLevel": "error"
}, },
"complexity": { "complexity": {
"noBannedTypes": "off", "noBannedTypes": "off",
@ -100,5 +101,17 @@
"formatter": { "formatter": {
"indentWidth": 2 "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 billableUsers = Math.max(eligibleUsers.length, minSeats);
const usersCost = seatPrice * billableUsers; const usersCost = seatPrice * billableUsers;
const includedTraffic = BILLING_INCLUDED_REQUESTS; const overageCost = useOverageCost(BILLING_INCLUDED_REQUESTS);
const overageCost = useOverageCost(includedTraffic);
const totalCost = usersCost + overageCost; const totalCost = usersCost + overageCost;

View File

@ -52,8 +52,7 @@ export const BillingDetailsPro = ({
const freeAssigned = Math.min(eligibleUsers.length, seats); const freeAssigned = Math.min(eligibleUsers.length, seats);
const paidAssigned = eligibleUsers.length - freeAssigned; const paidAssigned = eligibleUsers.length - freeAssigned;
const paidAssignedPrice = seatPrice * paidAssigned; const paidAssignedPrice = seatPrice * paidAssigned;
const includedTraffic = BILLING_INCLUDED_REQUESTS; const overageCost = useOverageCost(BILLING_INCLUDED_REQUESTS);
const overageCost = useOverageCost(includedTraffic);
const totalCost = planPrice + paidAssignedPrice + overageCost; 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'; import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useOverageCost = (includedTraffic: number) => { export const useOverageCost = (includedTraffic: number) => {
if (!includedTraffic) {
return 0;
}
const now = new Date(); const now = new Date();
const formatDate = (date: Date) => format(date, 'yyyy-MM-dd'); const formatDate = (date: Date) => format(date, 'yyyy-MM-dd');
const from = formatDate(startOfMonth(now)); const from = formatDate(startOfMonth(now));

View File

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

View File

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

View File

@ -5,5 +5,6 @@ const SearchHighlightContext = createContext('');
export const SearchHighlightProvider = SearchHighlightContext.Provider; export const SearchHighlightProvider = SearchHighlightContext.Provider;
export const useSearchHighlightContext = (): { searchQuery: string } => { 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 { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer'; import { render } from 'utils/testRenderer';
import { FeatureOverviewCell as makeFeatureOverviewCell } from './FeatureOverviewCell'; import { createFeatureOverviewCell } from './FeatureOverviewCell';
const noOp = () => {}; const noOp = () => {};
test('Display full overview information', () => { test('Display full overview information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp); const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render( render(
<FeatureOverviewCell <FeatureOverviewCell
@ -43,7 +43,7 @@ test('Display full overview information', () => {
}); });
test('Display minimal overview information', () => { test('Display minimal overview information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp); const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render( render(
<FeatureOverviewCell <FeatureOverviewCell
@ -69,7 +69,7 @@ test('Display minimal overview information', () => {
}); });
test('show archived information', () => { test('show archived information', () => {
const FeatureOverviewCell = makeFeatureOverviewCell(noOp, noOp); const FeatureOverviewCell = createFeatureOverviewCell(noOp, noOp);
render( render(
<FeatureOverviewCell <FeatureOverviewCell

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { TagTypeSelect } from './TagTypeSelect';
import { type TagOption, TagsInput } from './TagsInput'; import { type TagOption, TagsInput } from './TagsInput';
import useTags from 'hooks/api/getters/useTags/useTags'; import useTags from 'hooks/api/getters/useTags/useTags';
import useTagTypes from 'hooks/api/getters/useTagTypes/useTagTypes'; 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 { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useTagApi from 'hooks/api/actions/useTagApi/useTagApi'; import useTagApi from 'hooks/api/actions/useTagApi/useTagApi';
import type { TagSchema } from 'openapi'; import type { TagSchema } from 'openapi';

View File

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

View File

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

View File

@ -38,10 +38,10 @@ interface IProjectStatsProps {
} }
export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => { export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
const projectId = useRequiredPathParam('projectId');
if (Object.keys(stats).length === 0) { if (Object.keys(stats).length === 0) {
return null; return null;
} }
const projectId = useRequiredPathParam('projectId');
const { const {
avgTimeToProdCurrentWindow, 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 { type HeaderGroup, useGlobalFilter, useTable } from 'react-table';
import { Alert, Box, styled, Typography } from '@mui/material'; import { Alert, Box, styled, Typography } from '@mui/material';
import { import {
@ -41,8 +41,9 @@ const StyledBox = styled(Box)(({ theme }) => ({
}, },
})); }));
export const ChangeRequestTable: VFC = () => { export const ChangeRequestTable: FC = () => {
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const { hasAccess } = useContext(AccessContext);
const [dialogState, setDialogState] = useState<{ const [dialogState, setDialogState] = useState<{
isOpen: boolean; isOpen: boolean;
enableEnvironment: string; enableEnvironment: string;
@ -139,13 +140,8 @@ export const ChangeRequestTable: VFC = () => {
}, },
{ {
Header: 'Required approvals', Header: 'Required approvals',
Cell: ({ row: { original } }: any) => { Cell: ({ row: { original } }: any) =>
const { hasAccess } = useContext(AccessContext); original.changeRequestEnabled ? (
return (
<ConditionallyRender
condition={original.changeRequestEnabled}
show={
<StyledBox data-loading> <StyledBox data-loading>
<GeneralSelect <GeneralSelect
sx={{ width: '140px', marginLeft: 1 }} sx={{ width: '140px', marginLeft: 1 }}
@ -166,16 +162,11 @@ export const ChangeRequestTable: VFC = () => {
projectId, projectId,
) )
} }
IconComponent={ IconComponent={KeyboardArrowDownOutlined}
KeyboardArrowDownOutlined
}
fullWidth fullWidth
/> />
</StyledBox> </StyledBox>
} ) : null,
/>
);
},
width: 100, width: 100,
disableGlobalFilter: true, disableGlobalFilter: true,
disableSortBy: true, disableSortBy: true,