mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: application issues (#6347)
![image](https://github.com/Unleash/unleash/assets/964450/90153533-322c-46fd-8a1b-5853cbe0c35c)
This commit is contained in:
parent
3704956a06
commit
7cebf7b8fe
@ -0,0 +1,27 @@
|
|||||||
|
import { screen } from '@testing-library/react';
|
||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { ApplicationIssues } from './ApplicationIssues';
|
||||||
|
import { ApplicationOverviewIssuesSchema } from 'openapi';
|
||||||
|
|
||||||
|
test('Display all application issues', async () => {
|
||||||
|
const issues: ApplicationOverviewIssuesSchema[] = [
|
||||||
|
{
|
||||||
|
type: 'missingFeatures',
|
||||||
|
items: ['my-app'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'missingStrategies',
|
||||||
|
items: ['defaultStrategy', 'mainStrategy'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
render(<ApplicationIssues issues={issues} />);
|
||||||
|
|
||||||
|
await screen.findByText('my-app');
|
||||||
|
await screen.findByText('mainStrategy');
|
||||||
|
await screen.findByText(
|
||||||
|
`We detected 1 feature flag defined in the SDK that does not exist in Unleash`,
|
||||||
|
);
|
||||||
|
await screen.findByText(
|
||||||
|
`We detected 2 strategy types defined in the SDK that do not exist in Unleash`,
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,117 @@
|
|||||||
|
import { Box, styled } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { WarningAmberRounded } from '@mui/icons-material';
|
||||||
|
import { ApplicationOverviewIssuesSchema } from 'openapi';
|
||||||
|
|
||||||
|
const WarningContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
paddingBottom: theme.spacing(8),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const WarningHeader = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(2, 3, 2, 3),
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: theme.spacing(1.5),
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
|
||||||
|
border: `1px solid ${theme.palette.warning.border}`,
|
||||||
|
background: theme.palette.warning.light,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SmallText = styled(Box)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const WarningHeaderText = styled(SmallText)(({ theme }) => ({
|
||||||
|
color: theme.palette.warning.dark,
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledList = styled('ul')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(0, 0, 0, 2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledListElement = styled('li')(({ theme }) => ({
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const IssueContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
gap: theme.spacing(3),
|
||||||
|
borderRadius: ` 0 0 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`,
|
||||||
|
border: `1px solid ${theme.palette.warning.border}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const IssueTextContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface IApplicationIssuesProps {
|
||||||
|
issues: ApplicationOverviewIssuesSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveIssueText = (issue: ApplicationOverviewIssuesSchema) => {
|
||||||
|
const issueCount = issue.items.length;
|
||||||
|
let issueText = '';
|
||||||
|
|
||||||
|
switch (issue.type) {
|
||||||
|
case 'missingFeatures':
|
||||||
|
issueText = `feature flag${issueCount !== 1 ? 's' : ''}`;
|
||||||
|
break;
|
||||||
|
case 'missingStrategies':
|
||||||
|
issueText = `strategy type${issueCount !== 1 ? 's' : ''}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `We detected ${issueCount} ${issueText} defined in the SDK that ${
|
||||||
|
issueCount !== 1 ? 'do' : 'does'
|
||||||
|
} not exist in Unleash`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApplicationIssues = ({ issues }: IApplicationIssuesProps) => {
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={issues.length > 0}
|
||||||
|
show={
|
||||||
|
<WarningContainer>
|
||||||
|
<WarningHeader>
|
||||||
|
<WarningAmberRounded />
|
||||||
|
<WarningHeaderText>
|
||||||
|
We detected {issues.length} issues in this
|
||||||
|
application
|
||||||
|
</WarningHeaderText>
|
||||||
|
</WarningHeader>
|
||||||
|
<IssueContainer>
|
||||||
|
{issues.map((issue) => (
|
||||||
|
<IssueTextContainer key={issue.type}>
|
||||||
|
<SmallText>{resolveIssueText(issue)}</SmallText>
|
||||||
|
<StyledList>
|
||||||
|
{issue.items.map((item) => (
|
||||||
|
<StyledListElement key={item}>
|
||||||
|
{item}
|
||||||
|
</StyledListElement>
|
||||||
|
))}
|
||||||
|
</StyledList>
|
||||||
|
</IssueTextContainer>
|
||||||
|
))}
|
||||||
|
</IssueContainer>
|
||||||
|
</WarningContainer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -20,7 +20,7 @@ import { sortTypes } from 'utils/sortTypes';
|
|||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
||||||
import { ApplicationSchema } from '../../../openapi';
|
import { ApplicationSchema } from 'openapi';
|
||||||
|
|
||||||
export const ApplicationList = () => {
|
export const ApplicationList = () => {
|
||||||
const { applications: data, loading } = useApplications();
|
const { applications: data, loading } = useApplications();
|
||||||
|
@ -2,7 +2,7 @@ import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { styled, Typography, useTheme } from '@mui/material';
|
import { styled, Typography, useTheme } from '@mui/material';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ApplicationUsageSchema } from '../../../../openapi';
|
import { ApplicationUsageSchema } from 'openapi';
|
||||||
|
|
||||||
export interface IApplicationUsageCellProps {
|
export interface IApplicationUsageCellProps {
|
||||||
usage: ApplicationUsageSchema[] | undefined;
|
usage: ApplicationUsageSchema[] | undefined;
|
||||||
|
@ -2,7 +2,7 @@ import { screen } from '@testing-library/react';
|
|||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
import { PaginatedApplicationList } from './PaginatedApplicationList';
|
import { PaginatedApplicationList } from './PaginatedApplicationList';
|
||||||
import { ApplicationSchema } from '../../../openapi';
|
import { ApplicationSchema } from 'openapi';
|
||||||
|
|
||||||
const server = testServerSetup();
|
const server = testServerSetup();
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { PaginatedTable } from 'component/common/Table';
|
|||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
||||||
import { ApplicationSchema } from '../../../openapi';
|
import { ApplicationSchema } from 'openapi';
|
||||||
import {
|
import {
|
||||||
encodeQueryParams,
|
encodeQueryParams,
|
||||||
NumberParam,
|
NumberParam,
|
||||||
|
@ -2,7 +2,7 @@ import { screen } from '@testing-library/react';
|
|||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import { ApplicationOverviewSchema } from '../../openapi';
|
import { ApplicationOverviewSchema } from 'openapi';
|
||||||
import ApplicationOverview from './ApplicationOverview';
|
import ApplicationOverview from './ApplicationOverview';
|
||||||
|
|
||||||
const server = testServerSetup();
|
const server = testServerSetup();
|
||||||
|
@ -10,11 +10,11 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Check from '@mui/icons-material/CheckCircle';
|
|
||||||
import Warning from '@mui/icons-material/Warning';
|
|
||||||
import { ArcherContainer, ArcherElement } from 'react-archer';
|
import { ArcherContainer, ArcherElement } from 'react-archer';
|
||||||
import { FC, useLayoutEffect, useRef, useState } from 'react';
|
import { FC, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview';
|
import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview';
|
||||||
|
import { WarningAmberRounded } from '@mui/icons-material';
|
||||||
|
import { ApplicationIssues } from './ApplicationIssues/ApplicationIssues';
|
||||||
|
|
||||||
const StyledTable = styled('table')(({ theme }) => ({
|
const StyledTable = styled('table')(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
@ -33,8 +33,9 @@ const StyleApplicationContainer = styled(Box)(({ theme }) => ({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledApplicationBox = styled(Box)<{ mode: 'success' | 'warning' }>(
|
const StyledApplicationBox = styled(Box)<{
|
||||||
({ theme, mode }) => ({
|
mode: 'success' | 'warning';
|
||||||
|
}>(({ theme, mode }) => ({
|
||||||
borderRadius: theme.shape.borderRadiusMedium,
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderColor: theme.palette[mode].border,
|
borderColor: theme.palette[mode].border,
|
||||||
@ -43,21 +44,21 @@ const StyledApplicationBox = styled(Box)<{ mode: 'success' | 'warning' }>(
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: theme.spacing(1.5, 3, 2, 3),
|
padding: theme.spacing(1.5, 3, 2, 3),
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const StyledStatus = styled(Typography)<{ mode: 'success' | 'warning' }>(
|
const StyledStatus = styled(Typography)<{
|
||||||
({ theme, mode }) => ({
|
mode: 'success' | 'warning';
|
||||||
|
}>(({ theme, mode }) => ({
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
color: theme.palette[mode].dark,
|
color: theme.palette[mode].dark,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const StyledEnvironmentBox = styled(Box)<{ mode: 'success' | 'warning' }>(
|
const StyledEnvironmentBox = styled(Box)<{
|
||||||
({ theme, mode }) => ({
|
mode: 'success' | 'warning';
|
||||||
|
}>(({ theme, mode }) => ({
|
||||||
borderRadius: theme.shape.borderRadiusMedium,
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderColor:
|
borderColor:
|
||||||
@ -66,8 +67,7 @@ const StyledEnvironmentBox = styled(Box)<{ mode: 'success' | 'warning' }>(
|
|||||||
theme.palette[mode === 'success' ? 'secondary' : 'warning'].light,
|
theme.palette[mode === 'success' ? 'secondary' : 'warning'].light,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
padding: theme.spacing(1.5, 1.5, 1.5, 1.5),
|
padding: theme.spacing(1.5, 1.5, 1.5, 1.5),
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const StyledDivider = styled(Divider)(({ theme }) => ({
|
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -88,7 +88,7 @@ const EnvironmentHeader = styled(Typography)(({ theme }) => ({
|
|||||||
|
|
||||||
const SuccessStatus = () => (
|
const SuccessStatus = () => (
|
||||||
<StyledStatus mode='success'>
|
<StyledStatus mode='success'>
|
||||||
<Check
|
<WarningAmberRounded
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
color: theme.palette.success.main,
|
color: theme.palette.success.main,
|
||||||
})}
|
})}
|
||||||
@ -99,7 +99,7 @@ const SuccessStatus = () => (
|
|||||||
|
|
||||||
const WarningStatus: FC = ({ children }) => (
|
const WarningStatus: FC = ({ children }) => (
|
||||||
<StyledStatus mode='warning'>
|
<StyledStatus mode='warning'>
|
||||||
<Warning
|
<WarningAmberRounded
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
color: theme.palette.warning.main,
|
color: theme.palette.warning.main,
|
||||||
})}
|
})}
|
||||||
@ -116,7 +116,10 @@ const useElementWidth = () => {
|
|||||||
setWidth(`${elementRef.current?.scrollWidth}px`);
|
setWidth(`${elementRef.current?.scrollWidth}px`);
|
||||||
}, [elementRef, setWidth]);
|
}, [elementRef, setWidth]);
|
||||||
|
|
||||||
return { elementRef, width };
|
return {
|
||||||
|
elementRef,
|
||||||
|
width,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ApplicationOverview = () => {
|
export const ApplicationOverview = () => {
|
||||||
@ -135,13 +138,16 @@ export const ApplicationOverview = () => {
|
|||||||
|
|
||||||
const { elementRef, width } = useElementWidth();
|
const { elementRef, width } = useElementWidth();
|
||||||
|
|
||||||
const mode: 'success' | 'warning' = 'success';
|
const mode: 'success' | 'warning' =
|
||||||
|
data.issues.length === 0 ? 'success' : 'warning';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!loading && data.environments.length === 0}
|
condition={!loading && data.environments.length === 0}
|
||||||
show={<Alert severity='warning'>No data available.</Alert>}
|
show={<Alert severity='warning'>No data available.</Alert>}
|
||||||
elseShow={
|
elseShow={
|
||||||
|
<>
|
||||||
|
<ApplicationIssues issues={data.issues} />
|
||||||
<Box sx={{ width }}>
|
<Box sx={{ width }}>
|
||||||
<ArcherContainer
|
<ArcherContainer
|
||||||
strokeColor={theme.palette.secondary.border}
|
strokeColor={theme.palette.secondary.border}
|
||||||
@ -158,8 +164,8 @@ export const ApplicationOverview = () => {
|
|||||||
style: {
|
style: {
|
||||||
strokeColor:
|
strokeColor:
|
||||||
mode === 'success'
|
mode === 'success'
|
||||||
? theme.palette.secondary
|
? theme.palette
|
||||||
.border
|
.secondary.border
|
||||||
: theme.palette.warning
|
: theme.palette.warning
|
||||||
.border,
|
.border,
|
||||||
},
|
},
|
||||||
@ -178,8 +184,10 @@ export const ApplicationOverview = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
fontSize: theme.fontSizes.bodySize,
|
fontSize:
|
||||||
fontWeight: theme.fontWeight.bold,
|
theme.fontSizes.bodySize,
|
||||||
|
fontWeight:
|
||||||
|
theme.fontWeight.bold,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{applicationName}
|
{applicationName}
|
||||||
@ -192,7 +200,8 @@ export const ApplicationOverview = () => {
|
|||||||
show={<SuccessStatus />}
|
show={<SuccessStatus />}
|
||||||
elseShow={
|
elseShow={
|
||||||
<WarningStatus>
|
<WarningStatus>
|
||||||
3 issues detected
|
{data.issues.length} issues
|
||||||
|
detected
|
||||||
</WarningStatus>
|
</WarningStatus>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -233,7 +242,11 @@ export const ApplicationOverview = () => {
|
|||||||
<StyledCell>
|
<StyledCell>
|
||||||
{environment.sdks.map(
|
{environment.sdks.map(
|
||||||
(sdk) => (
|
(sdk) => (
|
||||||
<div key={sdk}>
|
<div
|
||||||
|
key={
|
||||||
|
sdk
|
||||||
|
}
|
||||||
|
>
|
||||||
{sdk}
|
{sdk}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -245,7 +258,9 @@ export const ApplicationOverview = () => {
|
|||||||
Last seen:
|
Last seen:
|
||||||
</StyledCell>
|
</StyledCell>
|
||||||
<StyledCell>
|
<StyledCell>
|
||||||
{environment.lastSeen}
|
{
|
||||||
|
environment.lastSeen
|
||||||
|
}
|
||||||
</StyledCell>
|
</StyledCell>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -256,6 +271,7 @@ export const ApplicationOverview = () => {
|
|||||||
</StyledEnvironmentsContainer>
|
</StyledEnvironmentsContainer>
|
||||||
</ArcherContainer>
|
</ArcherContainer>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -22,7 +22,7 @@ import { MetricsSummaryChart } from './MetricsSummaryChart/MetricsSummaryChart';
|
|||||||
import {
|
import {
|
||||||
ExecutiveSummarySchemaMetricsSummaryTrendsItem,
|
ExecutiveSummarySchemaMetricsSummaryTrendsItem,
|
||||||
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
||||||
} from '../../openapi';
|
} from 'openapi';
|
||||||
import { HealthStats } from './HealthStats/HealthStats';
|
import { HealthStats } from './HealthStats/HealthStats';
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { useFeedbackContext } from './useFeedback';
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { ProvideFeedbackSchema } from '../../openapi';
|
import { ProvideFeedbackSchema } from 'openapi';
|
||||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
|
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
|
||||||
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
|
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
@ -13,7 +13,7 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC
|
|||||||
import { useSearch } from 'hooks/useSearch';
|
import { useSearch } from 'hooks/useSearch';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FeedbackSchema } from '../../openapi';
|
import { FeedbackSchema } from 'openapi';
|
||||||
|
|
||||||
interface IFeedbackSchemaCellProps {
|
interface IFeedbackSchemaCellProps {
|
||||||
value?: string | null; // FIXME: proper type
|
value?: string | null; // FIXME: proper type
|
||||||
|
@ -2,7 +2,7 @@ import { screen } from '@testing-library/react';
|
|||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
import { ProjectApplications } from './ProjectApplications';
|
import { ProjectApplications } from './ProjectApplications';
|
||||||
import { ProjectApplicationSchema } from '../../../openapi';
|
import { ProjectApplicationSchema } from 'openapi';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import { SEARCH_INPUT } from 'utils/testIds';
|
import { SEARCH_INPUT } from 'utils/testIds';
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import useLoading from 'hooks/useLoading';
|
|||||||
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
||||||
import { withTableState } from 'utils/withTableState';
|
import { withTableState } from 'utils/withTableState';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { ProjectApplicationSchema } from '../../../openapi';
|
import { ProjectApplicationSchema } from 'openapi';
|
||||||
import mapValues from 'lodash.mapvalues';
|
import mapValues from 'lodash.mapvalues';
|
||||||
import {
|
import {
|
||||||
DEFAULT_PAGE_LIMIT,
|
DEFAULT_PAGE_LIMIT,
|
||||||
|
@ -3,6 +3,12 @@ import { formatApiPath } from 'utils/formatPath';
|
|||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
import { ApplicationOverviewSchema } from 'openapi';
|
import { ApplicationOverviewSchema } from 'openapi';
|
||||||
|
|
||||||
|
const placeHolderApplication: ApplicationOverviewSchema = {
|
||||||
|
environments: [],
|
||||||
|
featureCount: 0,
|
||||||
|
projects: [],
|
||||||
|
issues: [],
|
||||||
|
};
|
||||||
export const useApplicationOverview = (
|
export const useApplicationOverview = (
|
||||||
application: string,
|
application: string,
|
||||||
options: SWRConfiguration = {},
|
options: SWRConfiguration = {},
|
||||||
@ -17,7 +23,7 @@ export const useApplicationOverview = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: data || { environments: [], featureCount: 0, projects: [] },
|
data: data || placeHolderApplication,
|
||||||
error,
|
error,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user