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:
parent
a9490e6fe4
commit
6e947a8ba6
17
biome.json
17
biome.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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`,
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -436,7 +436,7 @@ const SecondaryFeatureInfo: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
export const FeatureOverviewCell =
|
||||
export const createFeatureOverviewCell =
|
||||
(
|
||||
onTagClick: (tag: string) => void,
|
||||
onFlagTypeClick: (type: string) => void,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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';
|
||||
|
@ -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', {
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user