mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-18 13:48:58 +02:00
Merge branch 'main' into chore/remove-deprecated-feature-update
This commit is contained in:
commit
c21512e18f
7
.github/workflows/build_doc_prs.yaml
vendored
7
.github/workflows/build_doc_prs.yaml
vendored
@ -17,4 +17,9 @@ jobs:
|
|||||||
UNLEASH_PROXY_URL: ${{ secrets.UNLEASH_PROXY_URL_DEVELOPMENT }}
|
UNLEASH_PROXY_URL: ${{ secrets.UNLEASH_PROXY_URL_DEVELOPMENT }}
|
||||||
run: |
|
run: |
|
||||||
# Build the site
|
# Build the site
|
||||||
cd website && yarn && yarn build
|
cd website && yarn
|
||||||
|
# give better error messages when the build fails (refer to website/readme.md#troubleshooting)
|
||||||
|
echo "Removing references to chalk in node_modules/@docusaurus/core/lib/client/serverEntry.js"
|
||||||
|
sed -i 's/chalk\(\w\|\.\)\+//g' node_modules/@docusaurus/core/lib/client/serverEntry.js
|
||||||
|
echo "Chalk removed"
|
||||||
|
yarn build
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"deep-diff": "1.0.2",
|
"deep-diff": "1.0.2",
|
||||||
"dequal": "2.0.3",
|
"dequal": "2.0.3",
|
||||||
"eslint": "8.48.0",
|
"eslint": "8.49.0",
|
||||||
"eslint-config-react-app": "7.0.1",
|
"eslint-config-react-app": "7.0.1",
|
||||||
"fast-json-patch": "3.1.1",
|
"fast-json-patch": "3.1.1",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
|
@ -34,6 +34,7 @@ import { RoleCell } from 'component/common/Table/cells/RoleCell/RoleCell';
|
|||||||
import { useSearch } from 'hooks/useSearch';
|
import { useSearch } from 'hooks/useSearch';
|
||||||
import { Download } from '@mui/icons-material';
|
import { Download } from '@mui/icons-material';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
const UsersList = () => {
|
const UsersList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -44,6 +45,7 @@ const UsersList = () => {
|
|||||||
const [pwDialog, setPwDialog] = useState<{ open: boolean; user?: IUser }>({
|
const [pwDialog, setPwDialog] = useState<{ open: boolean; user?: IUser }>({
|
||||||
open: false,
|
open: false,
|
||||||
});
|
});
|
||||||
|
const { isEnterprise } = useUiConfig();
|
||||||
const [delDialog, setDelDialog] = useState(false);
|
const [delDialog, setDelDialog] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [emailSent, setEmailSent] = useState(false);
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
@ -271,7 +273,10 @@ const UsersList = () => {
|
|||||||
<PageHeader.Divider />
|
<PageHeader.Divider />
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(accessOverviewEnabled)}
|
condition={
|
||||||
|
isEnterprise() &&
|
||||||
|
Boolean(accessOverviewEnabled)
|
||||||
|
}
|
||||||
show={() => (
|
show={() => (
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -18,7 +18,6 @@ export const TooltipResolver = ({
|
|||||||
if (!title && !titleComponent) {
|
if (!title && !titleComponent) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variant === 'custom') {
|
if (variant === 'custom') {
|
||||||
return (
|
return (
|
||||||
<HtmlTooltip {...rest} title={title || titleComponent} arrow>
|
<HtmlTooltip {...rest} title={title || titleComponent} arrow>
|
||||||
|
@ -12,6 +12,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
||||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
|
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
|
||||||
|
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
|
||||||
|
|
||||||
interface IStrategyItemProps {
|
interface IStrategyItemProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
@ -86,6 +87,9 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StrategyExecution strategy={strategy} />
|
<StrategyExecution strategy={strategy} />
|
||||||
|
{strategy.variants ? (
|
||||||
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
|
) : null}
|
||||||
</StrategyItemContainer>
|
</StrategyItemContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { Box, Typography, styled } from '@mui/material';
|
import { Box, Typography, styled } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
|
||||||
|
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
type SplitPreviewSliderProps = {
|
const StyledContainer = styled(Box)(() => ({
|
||||||
values: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@ -18,55 +17,188 @@ const StyledTrack = styled(Box)(({ theme }) => ({
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSegment = styled(Box)(({ theme }) => ({
|
const StyledSegment = styled(Box)(() => ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSegmentTrack = styled(Box)(({ theme }) => ({
|
const StyledSegmentTrack = styled(Box, {
|
||||||
height: theme.spacing(3),
|
shouldForwardProp: prop => prop !== 'index',
|
||||||
|
})<{ index: number }>(({ theme, index }) => ({
|
||||||
|
height: theme.spacing(1.8),
|
||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
background: theme.palette.variants[index % theme.palette.variants.length],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SplitPreviewSlider = ({ values }: SplitPreviewSliderProps) => {
|
const StyledHeaderContainer = styled(Box)(({ theme }) => ({
|
||||||
if (values.length < 2) {
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
marginY: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledVariantBoxContainer = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledVariantBox = styled(Box, {
|
||||||
|
shouldForwardProp: prop => prop !== 'index',
|
||||||
|
})<{ index: number }>(({ theme, index }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
'& div': {
|
||||||
|
width: theme.spacing(1.6),
|
||||||
|
height: theme.spacing(1.6),
|
||||||
|
borderRadius: '50%',
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
background:
|
||||||
|
theme.palette.variants[index % theme.palette.variants.length],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypographySubtitle = styled(Typography)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface ISplitPreviewSliderProps {
|
||||||
|
variants: IFeatureVariant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const SplitPreviewSlider = ({ variants }: ISplitPreviewSliderProps) => {
|
||||||
|
if (variants.length < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={theme => ({ marginTop: theme.spacing(2) })}>
|
<Box sx={theme => ({ marginTop: theme.spacing(2) })}>
|
||||||
<Typography
|
<SplitPreviewHeader variants={variants} />
|
||||||
variant="body2"
|
|
||||||
sx={theme => ({ marginY: theme.spacing(1) })}
|
|
||||||
>
|
|
||||||
Split preview
|
|
||||||
</Typography>
|
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledTrack />
|
<StyledTrack />
|
||||||
{values.map((value, index) => (
|
|
||||||
<StyledSegment key={index} sx={{ width: `${value}%` }}>
|
{variants.map((variant, index) => {
|
||||||
<StyledSegmentTrack
|
const value = variant.weight / 10;
|
||||||
sx={theme => ({
|
return (
|
||||||
background:
|
<TooltipResolver
|
||||||
theme.palette.variants[
|
variant="custom"
|
||||||
index % theme.palette.variants.length
|
key={index}
|
||||||
],
|
arrow
|
||||||
})}
|
onClick={e => e.preventDefault()}
|
||||||
/>
|
titleComponent={
|
||||||
<Typography
|
<SplitPreviewTooltip
|
||||||
variant="subtitle2"
|
variant={variant}
|
||||||
sx={theme => ({ marginTop: theme.spacing(1) })}
|
index={index}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{value}%
|
<Box
|
||||||
</Typography>
|
style={{
|
||||||
</StyledSegment>
|
width: `${value}%`,
|
||||||
))}
|
}}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
<StyledSegment>
|
||||||
|
<StyledSegmentTrack index={index} />
|
||||||
|
<StyledTypographySubtitle variant="subtitle2">
|
||||||
|
{value}%
|
||||||
|
</StyledTypographySubtitle>
|
||||||
|
</StyledSegment>
|
||||||
|
</Box>
|
||||||
|
</TooltipResolver>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SplitPreviewHeader = ({ variants }: ISplitPreviewSliderProps) => {
|
||||||
|
return (
|
||||||
|
<StyledHeaderContainer>
|
||||||
|
<StyledTypography variant="body2">
|
||||||
|
Feature variants ({variants.length})
|
||||||
|
</StyledTypography>
|
||||||
|
<StyledVariantBoxContainer>
|
||||||
|
{variants.map((variant, index) => (
|
||||||
|
<StyledVariantBox key={index} index={index}>
|
||||||
|
<Box />
|
||||||
|
<StyledTypography variant="body2">
|
||||||
|
{variant.name}
|
||||||
|
</StyledTypography>
|
||||||
|
</StyledVariantBox>
|
||||||
|
))}
|
||||||
|
</StyledVariantBoxContainer>
|
||||||
|
</StyledHeaderContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISplitPreviewTooltip {
|
||||||
|
variant: IFeatureVariant;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledTooltipContainer = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledVariantContainer = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
minWidth: '250px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledPayloadContainer = styled(Box)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledPayloadLabel = styled(Typography)(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SplitPreviewTooltip = ({ variant, index }: ISplitPreviewTooltip) => {
|
||||||
|
return (
|
||||||
|
<StyledTooltipContainer>
|
||||||
|
<StyledVariantContainer>
|
||||||
|
<StyledVariantBox index={index}>
|
||||||
|
<Box />
|
||||||
|
</StyledVariantBox>
|
||||||
|
|
||||||
|
<Typography variant="subtitle2">
|
||||||
|
{variant.weight / 10}% - {variant.name}
|
||||||
|
</Typography>
|
||||||
|
</StyledVariantContainer>
|
||||||
|
|
||||||
|
{variant.payload ? (
|
||||||
|
<StyledPayloadContainer>
|
||||||
|
<StyledPayloadLabel variant="body2">
|
||||||
|
Payload
|
||||||
|
</StyledPayloadLabel>
|
||||||
|
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={variant.payload.type === 'json'}
|
||||||
|
show={<code>{variant.payload.value}</code>}
|
||||||
|
elseShow={
|
||||||
|
<Typography variant="body2">
|
||||||
|
{variant.payload.value}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledPayloadContainer>
|
||||||
|
) : null}
|
||||||
|
</StyledTooltipContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default SplitPreviewSlider;
|
export default SplitPreviewSlider;
|
||||||
|
@ -157,9 +157,7 @@ export const StrategyVariants: FC<{
|
|||||||
>
|
>
|
||||||
Add variant
|
Add variant
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
<SplitPreviewSlider
|
<SplitPreviewSlider variants={variantsEdit} />
|
||||||
values={variantsEdit.map(variant => variant.weight / 10)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -43,7 +43,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import { IntegrationDelete } from './IntegrationDelete/IntegrationDelete';
|
import { IntegrationDelete } from './IntegrationDelete/IntegrationDelete';
|
||||||
import { IntegrationStateSwitch } from './IntegrationStateSwitch/IntegrationStateSwitch';
|
import { IntegrationStateSwitch } from './IntegrationStateSwitch/IntegrationStateSwitch';
|
||||||
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
import { capitalizeFirst } from 'utils/capitalizeFirst';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
|
||||||
import { IntegrationHowToSection } from '../IntegrationHowToSection/IntegrationHowToSection';
|
import { IntegrationHowToSection } from '../IntegrationHowToSection/IntegrationHowToSection';
|
||||||
|
|
||||||
type IntegrationFormProps = {
|
type IntegrationFormProps = {
|
||||||
@ -77,7 +76,6 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
label: event,
|
label: event,
|
||||||
}));
|
}));
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const integrationsRework = useUiFlag('integrationsRework');
|
|
||||||
const [formValues, setFormValues] = useState(initialValues);
|
const [formValues, setFormValues] = useState(initialValues);
|
||||||
const [errors, setErrors] = useState<{
|
const [errors, setErrors] = useState<{
|
||||||
containsErrors: boolean;
|
containsErrors: boolean;
|
||||||
@ -221,14 +219,14 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
try {
|
try {
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
await updateAddon(formValues as AddonSchema);
|
await updateAddon(formValues as AddonSchema);
|
||||||
navigate(integrationsRework ? '/integrations' : '/addons');
|
navigate('/integrations');
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Integration updated successfully',
|
title: 'Integration updated successfully',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await createAddon(formValues as Omit<AddonSchema, 'id'>);
|
await createAddon(formValues as Omit<AddonSchema, 'id'>);
|
||||||
navigate(integrationsRework ? '/integrations' : '/addons');
|
navigate('/integrations');
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
confetti: true,
|
confetti: true,
|
||||||
@ -393,19 +391,12 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledConfigurationSection>
|
</StyledConfigurationSection>
|
||||||
<ConditionallyRender
|
<Divider />
|
||||||
condition={Boolean(integrationsRework && editMode)}
|
<section>
|
||||||
show={() => (
|
<IntegrationDelete
|
||||||
<>
|
id={(formValues as AddonSchema).id}
|
||||||
<Divider />
|
/>
|
||||||
<section>
|
</section>
|
||||||
<IntegrationDelete
|
|
||||||
id={(formValues as AddonSchema).id}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { styled } from '@mui/material';
|
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
|
||||||
import { AddonTypeSchema } from 'openapi';
|
|
||||||
|
|
||||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
|
||||||
cursor: 'pointer',
|
|
||||||
marginLeft: theme.spacing(1),
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface IAddonNameCellProps {
|
|
||||||
provider: Pick<
|
|
||||||
AddonTypeSchema,
|
|
||||||
'displayName' | 'description' | 'deprecated'
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
|
||||||
*/
|
|
||||||
export const AddonNameCell = ({ provider }: IAddonNameCellProps) => (
|
|
||||||
<HighlightCell
|
|
||||||
value={provider.displayName}
|
|
||||||
subtitle={provider.description}
|
|
||||||
afterTitle={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(provider.deprecated)}
|
|
||||||
show={
|
|
||||||
<HtmlTooltip title={provider.deprecated} arrow>
|
|
||||||
<StyledBadge color="neutral">Deprecated</StyledBadge>
|
|
||||||
</HtmlTooltip>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
@ -1,21 +0,0 @@
|
|||||||
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
|
|
||||||
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated remove with `integrationsRework` flag
|
|
||||||
*/
|
|
||||||
export const AddonsList = () => {
|
|
||||||
const { providers, addons, loading } = useAddons();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={addons.length > 0}
|
|
||||||
show={<ConfiguredAddons />}
|
|
||||||
/>
|
|
||||||
<AvailableAddons loading={loading} providers={providers} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,172 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
SortableTableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableRow,
|
|
||||||
TablePlaceholder,
|
|
||||||
} from 'component/common/Table';
|
|
||||||
|
|
||||||
import { useTable, useSortBy } from 'react-table';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
|
||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
|
||||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
|
||||||
import { ConfigureAddonsButton } from './ConfigureAddonButton/ConfigureAddonsButton';
|
|
||||||
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
|
||||||
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
|
|
||||||
import type { AddonTypeSchema } from 'openapi';
|
|
||||||
|
|
||||||
interface IAvailableAddonsProps {
|
|
||||||
providers: AddonTypeSchema[];
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
|
||||||
*/
|
|
||||||
export const AvailableAddons = ({
|
|
||||||
providers,
|
|
||||||
loading,
|
|
||||||
}: IAvailableAddonsProps) => {
|
|
||||||
const data = useMemo(() => {
|
|
||||||
if (loading) {
|
|
||||||
return Array(5).fill({
|
|
||||||
name: 'Provider name',
|
|
||||||
description: 'Provider description when loading',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return providers.map(
|
|
||||||
({ name, displayName, description, deprecated, installation }) => ({
|
|
||||||
name,
|
|
||||||
displayName,
|
|
||||||
description,
|
|
||||||
deprecated,
|
|
||||||
installation,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [providers, loading]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
id: 'Icon',
|
|
||||||
Cell: ({
|
|
||||||
row: {
|
|
||||||
original: { name },
|
|
||||||
},
|
|
||||||
}: any) => {
|
|
||||||
return (
|
|
||||||
<IconCell
|
|
||||||
icon={<IntegrationIcon name={name as string} />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Name',
|
|
||||||
accessor: 'name',
|
|
||||||
width: '90%',
|
|
||||||
Cell: ({ row: { original } }: any) => (
|
|
||||||
<AddonNameCell provider={original} />
|
|
||||||
),
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Actions',
|
|
||||||
align: 'center',
|
|
||||||
Cell: ({ row: { original } }: any) => (
|
|
||||||
<ActionCell>
|
|
||||||
<ConfigureAddonsButton provider={original} />
|
|
||||||
</ActionCell>
|
|
||||||
),
|
|
||||||
width: 150,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessor: 'description',
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialState = useMemo(
|
|
||||||
() => ({
|
|
||||||
sortBy: [{ id: 'name', desc: false }],
|
|
||||||
hiddenColumns: ['description'],
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
getTableProps,
|
|
||||||
getTableBodyProps,
|
|
||||||
headerGroups,
|
|
||||||
rows,
|
|
||||||
prepareRow,
|
|
||||||
state: { globalFilter },
|
|
||||||
} = useTable(
|
|
||||||
{
|
|
||||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
|
||||||
data,
|
|
||||||
initialState,
|
|
||||||
sortTypes,
|
|
||||||
autoResetGlobalFilter: false,
|
|
||||||
autoResetSortBy: false,
|
|
||||||
disableSortRemove: true,
|
|
||||||
},
|
|
||||||
useSortBy
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent
|
|
||||||
isLoading={loading}
|
|
||||||
header={<PageHeader title={`Available addons (${rows.length})`} />}
|
|
||||||
>
|
|
||||||
<Table {...getTableProps()}>
|
|
||||||
<SortableTableHeader headerGroups={headerGroups} />
|
|
||||||
<TableBody {...getTableBodyProps()}>
|
|
||||||
{rows.map(row => {
|
|
||||||
prepareRow(row);
|
|
||||||
return (
|
|
||||||
<TableRow hover {...row.getRowProps()}>
|
|
||||||
{row.cells.map(cell => (
|
|
||||||
<TableCell {...cell.getCellProps()}>
|
|
||||||
{cell.render('Cell')}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={rows.length === 0}
|
|
||||||
show={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={globalFilter?.length > 0}
|
|
||||||
show={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No providers found matching “
|
|
||||||
{globalFilter}
|
|
||||||
”
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No providers available.
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
|
||||||
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import type { AddonTypeSchema } from 'openapi';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface IConfigureAddonsButtonProps {
|
|
||||||
provider: AddonTypeSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
|
||||||
*/
|
|
||||||
export const ConfigureAddonsButton = ({
|
|
||||||
provider,
|
|
||||||
}: IConfigureAddonsButtonProps) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PermissionButton
|
|
||||||
permission={CREATE_ADDON}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/addons/create/${provider.name}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Configure
|
|
||||||
</PermissionButton>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,241 +0,0 @@
|
|||||||
import { Table, TableBody, TableCell, TableRow } from 'component/common/Table';
|
|
||||||
import { useMemo, useState, useCallback } from 'react';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
|
||||||
import { useTable, useSortBy } from 'react-table';
|
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
||||||
import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
|
|
||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
|
||||||
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
|
||||||
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
|
|
||||||
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
|
|
||||||
import { AddonSchema } from 'openapi';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
|
||||||
*/
|
|
||||||
export const ConfiguredAddons = () => {
|
|
||||||
const { refetchAddons, addons, providers, loading } = useAddons();
|
|
||||||
const { updateAddon, removeAddon } = useAddonsApi();
|
|
||||||
const { setToastData, setToastApiError } = useToast();
|
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
|
||||||
const [deletedAddon, setDeletedAddon] = useState<AddonSchema>({
|
|
||||||
id: 0,
|
|
||||||
provider: '',
|
|
||||||
description: '',
|
|
||||||
enabled: false,
|
|
||||||
events: [],
|
|
||||||
parameters: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = useMemo(() => {
|
|
||||||
if (loading) {
|
|
||||||
return Array(5).fill({
|
|
||||||
name: 'Addon name',
|
|
||||||
description: 'Addon description when loading',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return addons.map(addon => ({
|
|
||||||
...addon,
|
|
||||||
}));
|
|
||||||
}, [addons, loading]);
|
|
||||||
|
|
||||||
const toggleAddon = useCallback(
|
|
||||||
async (addon: AddonSchema) => {
|
|
||||||
try {
|
|
||||||
await updateAddon({ ...addon, enabled: !addon.enabled });
|
|
||||||
refetchAddons();
|
|
||||||
setToastData({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Success',
|
|
||||||
text: !addon.enabled
|
|
||||||
? 'Addon is now enabled'
|
|
||||||
: 'Addon is now disabled',
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
throw error; // caught by optimistic update
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setToastApiError, refetchAddons, setToastData, updateAddon]
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
accessor: 'id',
|
|
||||||
Cell: ({
|
|
||||||
row: {
|
|
||||||
original: { provider },
|
|
||||||
},
|
|
||||||
}: any) => (
|
|
||||||
<IconCell
|
|
||||||
icon={<IntegrationIcon name={provider as string} />}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Name',
|
|
||||||
accessor: 'provider',
|
|
||||||
width: '90%',
|
|
||||||
Cell: ({
|
|
||||||
row: {
|
|
||||||
original: { provider, description },
|
|
||||||
},
|
|
||||||
}: any) => (
|
|
||||||
<AddonNameCell
|
|
||||||
provider={{
|
|
||||||
...(providers.find(
|
|
||||||
({ name }) => name === provider
|
|
||||||
) || {
|
|
||||||
displayName: provider,
|
|
||||||
}),
|
|
||||||
description,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Actions',
|
|
||||||
id: 'Actions',
|
|
||||||
align: 'center',
|
|
||||||
Cell: ({
|
|
||||||
row: { original },
|
|
||||||
}: {
|
|
||||||
row: { original: AddonSchema };
|
|
||||||
}) => (
|
|
||||||
<ConfiguredAddonsActionsCell
|
|
||||||
key={original.id}
|
|
||||||
setShowDelete={setShowDelete}
|
|
||||||
toggleAddon={toggleAddon}
|
|
||||||
setDeletedAddon={setDeletedAddon}
|
|
||||||
original={original}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
width: 150,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessor: 'description',
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[toggleAddon]
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialState = useMemo(
|
|
||||||
() => ({
|
|
||||||
sortBy: [
|
|
||||||
{ id: 'provider', desc: false },
|
|
||||||
{ id: 'id', desc: false },
|
|
||||||
],
|
|
||||||
hiddenColumns: ['description'],
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
getTableProps,
|
|
||||||
getTableBodyProps,
|
|
||||||
headerGroups,
|
|
||||||
rows,
|
|
||||||
prepareRow,
|
|
||||||
state: { globalFilter },
|
|
||||||
} = useTable(
|
|
||||||
{
|
|
||||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
|
||||||
data,
|
|
||||||
initialState,
|
|
||||||
sortTypes,
|
|
||||||
autoResetGlobalFilter: false,
|
|
||||||
autoResetSortBy: false,
|
|
||||||
disableSortRemove: true,
|
|
||||||
},
|
|
||||||
useSortBy
|
|
||||||
);
|
|
||||||
|
|
||||||
const onRemoveAddon = async (addon: AddonSchema) => {
|
|
||||||
try {
|
|
||||||
await removeAddon(addon.id);
|
|
||||||
refetchAddons();
|
|
||||||
setToastData({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Success',
|
|
||||||
text: 'Deleted addon successfully',
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent
|
|
||||||
isLoading={loading}
|
|
||||||
header={<PageHeader title={`Configured addons (${rows.length})`} />}
|
|
||||||
sx={theme => ({ marginBottom: theme.spacing(2) })}
|
|
||||||
>
|
|
||||||
<Table {...getTableProps()}>
|
|
||||||
<SortableTableHeader headerGroups={headerGroups} />
|
|
||||||
<TableBody {...getTableBodyProps()}>
|
|
||||||
{rows.map(row => {
|
|
||||||
prepareRow(row);
|
|
||||||
return (
|
|
||||||
<TableRow hover {...row.getRowProps()}>
|
|
||||||
{row.cells.map(cell => (
|
|
||||||
<TableCell
|
|
||||||
{...cell.getCellProps()}
|
|
||||||
padding="none"
|
|
||||||
>
|
|
||||||
{cell.render('Cell')}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={rows.length === 0}
|
|
||||||
show={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={globalFilter?.length > 0}
|
|
||||||
show={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No addons found matching “
|
|
||||||
{globalFilter}
|
|
||||||
”
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No addons configured
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Dialogue
|
|
||||||
open={showDelete}
|
|
||||||
onClick={() => {
|
|
||||||
onRemoveAddon(deletedAddon);
|
|
||||||
setShowDelete(false);
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
setShowDelete(false);
|
|
||||||
}}
|
|
||||||
title="Confirm deletion"
|
|
||||||
>
|
|
||||||
<div>Are you sure you want to delete this Addon?</div>
|
|
||||||
</Dialogue>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,76 +0,0 @@
|
|||||||
import { Edit, Delete } from '@mui/icons-material';
|
|
||||||
import { Tooltip } from '@mui/material';
|
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
|
||||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
|
||||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
|
||||||
import { useOptimisticUpdate } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/hooks/useOptimisticUpdate';
|
|
||||||
import {
|
|
||||||
UPDATE_ADDON,
|
|
||||||
DELETE_ADDON,
|
|
||||||
} from 'component/providers/AccessProvider/permissions';
|
|
||||||
import { AddonSchema } from 'openapi';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface IConfiguredAddonsActionsCellProps {
|
|
||||||
toggleAddon: (addon: AddonSchema) => Promise<void>;
|
|
||||||
original: AddonSchema;
|
|
||||||
setShowDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
setDeletedAddon: React.Dispatch<React.SetStateAction<AddonSchema>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
|
||||||
*/
|
|
||||||
export const ConfiguredAddonsActionsCell = ({
|
|
||||||
toggleAddon,
|
|
||||||
setShowDelete,
|
|
||||||
setDeletedAddon,
|
|
||||||
original,
|
|
||||||
}: IConfiguredAddonsActionsCellProps) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [isEnabled, setIsEnabled, rollbackIsChecked] =
|
|
||||||
useOptimisticUpdate<boolean>(original.enabled);
|
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
setIsEnabled(!isEnabled);
|
|
||||||
toggleAddon(original).catch(rollbackIsChecked);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ActionCell>
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
isEnabled
|
|
||||||
? `Disable addon ${original.provider}`
|
|
||||||
: `Enable addon ${original.provider}`
|
|
||||||
}
|
|
||||||
arrow
|
|
||||||
describeChild
|
|
||||||
>
|
|
||||||
<PermissionSwitch
|
|
||||||
permission={UPDATE_ADDON}
|
|
||||||
checked={isEnabled}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<ActionCell.Divider />
|
|
||||||
<PermissionIconButton
|
|
||||||
permission={UPDATE_ADDON}
|
|
||||||
tooltipProps={{ title: 'Edit Addon' }}
|
|
||||||
onClick={() => navigate(`/addons/edit/${original.id}`)}
|
|
||||||
>
|
|
||||||
<Edit />
|
|
||||||
</PermissionIconButton>
|
|
||||||
<PermissionIconButton
|
|
||||||
permission={DELETE_ADDON}
|
|
||||||
tooltipProps={{ title: 'Remove Addon' }}
|
|
||||||
onClick={() => {
|
|
||||||
setDeletedAddon(original);
|
|
||||||
setShowDelete(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Delete />
|
|
||||||
</PermissionIconButton>
|
|
||||||
</ActionCell>
|
|
||||||
);
|
|
||||||
};
|
|
@ -308,11 +308,7 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"menu": {
|
"menu": {},
|
||||||
"advanced": true,
|
|
||||||
"mobile": true,
|
|
||||||
},
|
|
||||||
"notFlag": "integrationsRework",
|
|
||||||
"path": "/addons",
|
"path": "/addons",
|
||||||
"title": "Addons",
|
"title": "Addons",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
@ -343,7 +339,6 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"flag": "integrationsRework",
|
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"menu": {
|
"menu": {
|
||||||
"advanced": true,
|
"advanced": true,
|
||||||
|
@ -43,9 +43,9 @@ import { LazyAdmin } from 'component/admin/LazyAdmin';
|
|||||||
import { LazyProject } from 'component/project/Project/LazyProject';
|
import { LazyProject } from 'component/project/Project/LazyProject';
|
||||||
import { LoginHistory } from 'component/loginHistory/LoginHistory';
|
import { LoginHistory } from 'component/loginHistory/LoginHistory';
|
||||||
import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList';
|
import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList';
|
||||||
import { AddonsList } from 'component/integrations/IntegrationList/AddonsList';
|
|
||||||
import { ViewIntegration } from 'component/integrations/ViewIntegration/ViewIntegration';
|
import { ViewIntegration } from 'component/integrations/ViewIntegration/ViewIntegration';
|
||||||
import { ApplicationList } from '../application/ApplicationList/ApplicationList';
|
import { ApplicationList } from '../application/ApplicationList/ApplicationList';
|
||||||
|
import { AddonRedirect } from 'component/integrations/AddonRedirect/AddonRedirect';
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
// Splash
|
// Splash
|
||||||
@ -306,8 +306,7 @@ export const routes: IRoute[] = [
|
|||||||
path: '/addons/create/:providerId',
|
path: '/addons/create/:providerId',
|
||||||
parent: '/addons',
|
parent: '/addons',
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
component: CreateIntegration,
|
component: AddonRedirect,
|
||||||
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
|
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
@ -315,21 +314,17 @@ export const routes: IRoute[] = [
|
|||||||
path: '/addons/edit/:addonId',
|
path: '/addons/edit/:addonId',
|
||||||
parent: '/addons',
|
parent: '/addons',
|
||||||
title: 'Edit',
|
title: 'Edit',
|
||||||
component: EditIntegration,
|
component: AddonRedirect,
|
||||||
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
|
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/addons',
|
path: '/addons',
|
||||||
title: 'Addons',
|
title: 'Addons',
|
||||||
component: AddonsList,
|
component: AddonRedirect,
|
||||||
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
|
|
||||||
hidden: false,
|
hidden: false,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
notFlag: 'integrationsRework',
|
menu: {},
|
||||||
menu: { mobile: true, advanced: true },
|
|
||||||
// TODO: remove 'addons' from menu after removing `integrationsRework` menu flag
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/integrations/create/:providerId',
|
path: '/integrations/create/:providerId',
|
||||||
@ -362,7 +357,6 @@ export const routes: IRoute[] = [
|
|||||||
hidden: false,
|
hidden: false,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { mobile: true, advanced: true },
|
menu: { mobile: true, advanced: true },
|
||||||
flag: 'integrationsRework',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Segments
|
// Segments
|
||||||
|
@ -59,7 +59,6 @@ export type UiFlags = {
|
|||||||
customRootRolesKillSwitch?: boolean;
|
customRootRolesKillSwitch?: boolean;
|
||||||
strategyVariant?: boolean;
|
strategyVariant?: boolean;
|
||||||
lastSeenByEnvironment?: boolean;
|
lastSeenByEnvironment?: boolean;
|
||||||
integrationsRework?: boolean;
|
|
||||||
multipleRoles?: boolean;
|
multipleRoles?: boolean;
|
||||||
featureNamingPattern?: boolean;
|
featureNamingPattern?: boolean;
|
||||||
doraMetrics?: boolean;
|
doraMetrics?: boolean;
|
||||||
|
@ -1845,10 +1845,10 @@
|
|||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
"@eslint/js@8.48.0":
|
"@eslint/js@8.49.0":
|
||||||
version "8.48.0"
|
version "8.49.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb"
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333"
|
||||||
integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==
|
integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==
|
||||||
|
|
||||||
"@exodus/schemasafe@^1.0.0-rc.2":
|
"@exodus/schemasafe@^1.0.0-rc.2":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@ -1860,10 +1860,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz#1a106721368dba5e7e9fb7e9a3a6f9efbd8df36d"
|
resolved "https://registry.yarnpkg.com/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz#1a106721368dba5e7e9fb7e9a3a6f9efbd8df36d"
|
||||||
integrity sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==
|
integrity sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==
|
||||||
|
|
||||||
"@humanwhocodes/config-array@^0.11.10":
|
"@humanwhocodes/config-array@^0.11.11":
|
||||||
version "0.11.10"
|
version "0.11.11"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
|
||||||
integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==
|
integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@humanwhocodes/object-schema" "^1.2.1"
|
"@humanwhocodes/object-schema" "^1.2.1"
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
@ -5357,16 +5357,16 @@ eslint-visitor-keys@^3.4.3:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||||
|
|
||||||
eslint@8.48.0:
|
eslint@8.49.0:
|
||||||
version "8.48.0"
|
version "8.49.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42"
|
||||||
integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
|
integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.2.0"
|
"@eslint-community/eslint-utils" "^4.2.0"
|
||||||
"@eslint-community/regexpp" "^4.6.1"
|
"@eslint-community/regexpp" "^4.6.1"
|
||||||
"@eslint/eslintrc" "^2.1.2"
|
"@eslint/eslintrc" "^2.1.2"
|
||||||
"@eslint/js" "8.48.0"
|
"@eslint/js" "8.49.0"
|
||||||
"@humanwhocodes/config-array" "^0.11.10"
|
"@humanwhocodes/config-array" "^0.11.11"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
"@nodelib/fs.walk" "^1.2.8"
|
"@nodelib/fs.walk" "^1.2.8"
|
||||||
ajv "^6.12.4"
|
ajv "^6.12.4"
|
||||||
|
@ -205,10 +205,10 @@
|
|||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"eslint-plugin-regexp": "^1.14.0",
|
"eslint-plugin-regexp": "^1.14.0",
|
||||||
"faker": "5.5.3",
|
"faker": "5.5.3",
|
||||||
"fast-check": "3.12.1",
|
"fast-check": "3.13.0",
|
||||||
"fetch-mock": "9.11.0",
|
"fetch-mock": "9.11.0",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"jest": "29.6.4",
|
"jest": "29.7.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"lint-staged": "13.2.3",
|
"lint-staged": "13.2.3",
|
||||||
"nock": "13.3.3",
|
"nock": "13.3.3",
|
||||||
|
@ -87,7 +87,6 @@ exports[`should create default config 1`] = `
|
|||||||
"featuresExportImport": true,
|
"featuresExportImport": true,
|
||||||
"filterInvalidClientMetrics": false,
|
"filterInvalidClientMetrics": false,
|
||||||
"googleAuthEnabled": false,
|
"googleAuthEnabled": false,
|
||||||
"integrationsRework": false,
|
|
||||||
"lastSeenByEnvironment": false,
|
"lastSeenByEnvironment": false,
|
||||||
"maintenanceMode": false,
|
"maintenanceMode": false,
|
||||||
"messageBanner": {
|
"messageBanner": {
|
||||||
@ -127,7 +126,6 @@ exports[`should create default config 1`] = `
|
|||||||
"featuresExportImport": true,
|
"featuresExportImport": true,
|
||||||
"filterInvalidClientMetrics": false,
|
"filterInvalidClientMetrics": false,
|
||||||
"googleAuthEnabled": false,
|
"googleAuthEnabled": false,
|
||||||
"integrationsRework": false,
|
|
||||||
"lastSeenByEnvironment": false,
|
"lastSeenByEnvironment": false,
|
||||||
"maintenanceMode": false,
|
"maintenanceMode": false,
|
||||||
"messageBanner": {
|
"messageBanner": {
|
||||||
|
@ -19,6 +19,7 @@ beforeAll(async () => {
|
|||||||
advancedPlayground: true,
|
advancedPlayground: true,
|
||||||
strictSchemaValidation: true,
|
strictSchemaValidation: true,
|
||||||
strategyVariant: true,
|
strategyVariant: true,
|
||||||
|
privateProjects: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ import { IUnleashServices } from 'lib/types/services';
|
|||||||
import { ALL } from '../../types/models/api-token';
|
import { ALL } from '../../types/models/api-token';
|
||||||
import { PlaygroundFeatureSchema } from 'lib/openapi/spec/playground-feature-schema';
|
import { PlaygroundFeatureSchema } from 'lib/openapi/spec/playground-feature-schema';
|
||||||
import { Logger } from '../../logger';
|
import { Logger } from '../../logger';
|
||||||
import { ISegment, IUnleashConfig } from 'lib/types';
|
import { IFlagResolver, ISegment, IUnleashConfig } from 'lib/types';
|
||||||
import { offlineUnleashClient } from './offline-unleash-client';
|
import { offlineUnleashClient } from './offline-unleash-client';
|
||||||
import { FeatureInterface } from 'lib/features/playground/feature-evaluator/feature';
|
import { FeatureInterface } from 'lib/features/playground/feature-evaluator/feature';
|
||||||
import {
|
import {
|
||||||
@ -16,10 +16,11 @@ import { FeatureConfigurationClient } from '../../types/stores/feature-strategie
|
|||||||
import { generateObjectCombinations } from './generateObjectCombinations';
|
import { generateObjectCombinations } from './generateObjectCombinations';
|
||||||
import groupBy from 'lodash.groupby';
|
import groupBy from 'lodash.groupby';
|
||||||
import { omitKeys } from '../../util';
|
import { omitKeys } from '../../util';
|
||||||
import { AdvancedPlaygroundFeatureSchema } from '../../openapi/spec/advanced-playground-feature-schema';
|
import { AdvancedPlaygroundFeatureSchema } from '../../openapi';
|
||||||
import { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/spec/advanced-playground-environment-feature-schema';
|
import { AdvancedPlaygroundEnvironmentFeatureSchema } from '../../openapi/spec/advanced-playground-environment-feature-schema';
|
||||||
import { validateQueryComplexity } from './validateQueryComplexity';
|
import { validateQueryComplexity } from './validateQueryComplexity';
|
||||||
import { playgroundStrategyEvaluation } from 'lib/openapi';
|
import { playgroundStrategyEvaluation } from 'lib/openapi';
|
||||||
|
import { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
|
||||||
|
|
||||||
type EvaluationInput = {
|
type EvaluationInput = {
|
||||||
features: FeatureConfigurationClient[];
|
features: FeatureConfigurationClient[];
|
||||||
@ -66,16 +67,28 @@ export class PlaygroundService {
|
|||||||
|
|
||||||
private readonly segmentService: ISegmentService;
|
private readonly segmentService: ISegmentService;
|
||||||
|
|
||||||
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
private privateProjectChecker: IPrivateProjectChecker;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{
|
{
|
||||||
featureToggleServiceV2,
|
featureToggleServiceV2,
|
||||||
segmentService,
|
segmentService,
|
||||||
}: Pick<IUnleashServices, 'featureToggleServiceV2' | 'segmentService'>,
|
privateProjectChecker,
|
||||||
|
}: Pick<
|
||||||
|
IUnleashServices,
|
||||||
|
| 'featureToggleServiceV2'
|
||||||
|
| 'segmentService'
|
||||||
|
| 'privateProjectChecker'
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
this.logger = config.getLogger('services/playground-service.ts');
|
this.logger = config.getLogger('services/playground-service.ts');
|
||||||
|
this.flagResolver = config.flagResolver;
|
||||||
this.featureToggleService = featureToggleServiceV2;
|
this.featureToggleService = featureToggleServiceV2;
|
||||||
this.segmentService = segmentService;
|
this.segmentService = segmentService;
|
||||||
|
this.privateProjectChecker = privateProjectChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateAdvancedQuery(
|
async evaluateAdvancedQuery(
|
||||||
@ -83,10 +96,29 @@ export class PlaygroundService {
|
|||||||
environments: string[],
|
environments: string[],
|
||||||
context: SdkContextSchema,
|
context: SdkContextSchema,
|
||||||
limit: number,
|
limit: number,
|
||||||
|
userId: number,
|
||||||
): Promise<AdvancedPlaygroundFeatureEvaluationResult[]> {
|
): Promise<AdvancedPlaygroundFeatureEvaluationResult[]> {
|
||||||
const segments = await this.segmentService.getActive();
|
const segments = await this.segmentService.getActive();
|
||||||
|
|
||||||
|
let filteredProjects: typeof projects;
|
||||||
|
if (this.flagResolver.isEnabled('privateProjects')) {
|
||||||
|
const accessibleProjects =
|
||||||
|
await this.privateProjectChecker.getUserAccessibleProjects(
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
filteredProjects =
|
||||||
|
projects === ALL
|
||||||
|
? accessibleProjects
|
||||||
|
: projects.filter((project) =>
|
||||||
|
accessibleProjects.includes(project),
|
||||||
|
);
|
||||||
|
console.log(accessibleProjects);
|
||||||
|
}
|
||||||
|
|
||||||
const environmentFeatures = await Promise.all(
|
const environmentFeatures = await Promise.all(
|
||||||
environments.map((env) => this.resolveFeatures(projects, env)),
|
environments.map((env) =>
|
||||||
|
this.resolveFeatures(filteredProjects, env),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const contexts = generateObjectCombinations(context);
|
const contexts = generateObjectCombinations(context);
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
advancedPlaygroundViewModel,
|
advancedPlaygroundViewModel,
|
||||||
playgroundViewModel,
|
playgroundViewModel,
|
||||||
} from './playground-view-model';
|
} from './playground-view-model';
|
||||||
|
import { IAuthRequest } from '../../routes/unleash-types';
|
||||||
|
|
||||||
export default class PlaygroundController extends Controller {
|
export default class PlaygroundController extends Controller {
|
||||||
private openApiService: OpenApiService;
|
private openApiService: OpenApiService;
|
||||||
@ -112,9 +113,10 @@ export default class PlaygroundController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async evaluateAdvancedContext(
|
async evaluateAdvancedContext(
|
||||||
req: Request<any, any, AdvancedPlaygroundRequestSchema>,
|
req: IAuthRequest<any, any, AdvancedPlaygroundRequestSchema>,
|
||||||
res: Response<AdvancedPlaygroundResponseSchema>,
|
res: Response<AdvancedPlaygroundResponseSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { user } = req;
|
||||||
// used for runtime control, do not remove
|
// used for runtime control, do not remove
|
||||||
const { payload } = this.flagResolver.getVariant('advancedPlayground');
|
const { payload } = this.flagResolver.getVariant('advancedPlayground');
|
||||||
const limit =
|
const limit =
|
||||||
@ -127,6 +129,7 @@ export default class PlaygroundController extends Controller {
|
|||||||
req.body.environments,
|
req.body.environments,
|
||||||
req.body.context,
|
req.body.context,
|
||||||
limit,
|
limit,
|
||||||
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const response: AdvancedPlaygroundResponseSchema =
|
const response: AdvancedPlaygroundResponseSchema =
|
||||||
|
@ -20,6 +20,7 @@ import { clientMetricsSchema } from './schema';
|
|||||||
import { PartialSome } from '../../types/partial';
|
import { PartialSome } from '../../types/partial';
|
||||||
import { IPrivateProjectChecker } from '../../features/private-project/privateProjectCheckerType';
|
import { IPrivateProjectChecker } from '../../features/private-project/privateProjectCheckerType';
|
||||||
import { IFlagResolver } from '../../types';
|
import { IFlagResolver } from '../../types';
|
||||||
|
import { ALL_PROJECTS } from '../../util';
|
||||||
|
|
||||||
export default class ClientInstanceService {
|
export default class ClientInstanceService {
|
||||||
apps = {};
|
apps = {};
|
||||||
@ -178,7 +179,7 @@ export default class ClientInstanceService {
|
|||||||
): Promise<IClientApplication[]> {
|
): Promise<IClientApplication[]> {
|
||||||
const applications =
|
const applications =
|
||||||
await this.clientApplicationsStore.getAppsForStrategy(query);
|
await this.clientApplicationsStore.getAppsForStrategy(query);
|
||||||
if (this.flagResolver.isEnabled('privateProjects') && userId) {
|
if (this.flagResolver.isEnabled('privateProjects')) {
|
||||||
const accessibleProjects =
|
const accessibleProjects =
|
||||||
await this.privateProjectChecker.getUserAccessibleProjects(
|
await this.privateProjectChecker.getUserAccessibleProjects(
|
||||||
userId,
|
userId,
|
||||||
@ -188,7 +189,7 @@ export default class ClientInstanceService {
|
|||||||
...application,
|
...application,
|
||||||
usage: application.usage?.filter(
|
usage: application.usage?.filter(
|
||||||
(usageItem) =>
|
(usageItem) =>
|
||||||
usageItem.project === '*' ||
|
usageItem.project === ALL_PROJECTS ||
|
||||||
accessibleProjects.includes(usageItem.project),
|
accessibleProjects.includes(usageItem.project),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -251,6 +251,7 @@ export const createServices = (
|
|||||||
const playgroundService = new PlaygroundService(config, {
|
const playgroundService = new PlaygroundService(config, {
|
||||||
featureToggleServiceV2,
|
featureToggleServiceV2,
|
||||||
segmentService,
|
segmentService,
|
||||||
|
privateProjectChecker,
|
||||||
});
|
});
|
||||||
|
|
||||||
const configurationRevisionService = new ConfigurationRevisionService(
|
const configurationRevisionService = new ConfigurationRevisionService(
|
||||||
|
@ -24,7 +24,6 @@ export type IFlagKey =
|
|||||||
| 'filterInvalidClientMetrics'
|
| 'filterInvalidClientMetrics'
|
||||||
| 'lastSeenByEnvironment'
|
| 'lastSeenByEnvironment'
|
||||||
| 'customRootRolesKillSwitch'
|
| 'customRootRolesKillSwitch'
|
||||||
| 'integrationsRework'
|
|
||||||
| 'multipleRoles'
|
| 'multipleRoles'
|
||||||
| 'featureNamingPattern'
|
| 'featureNamingPattern'
|
||||||
| 'doraMetrics'
|
| 'doraMetrics'
|
||||||
@ -116,10 +115,6 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH,
|
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
integrationsRework: parseEnvVarBoolean(
|
|
||||||
process.env.UNLEASH_INTEGRATIONS,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
multipleRoles: parseEnvVarBoolean(
|
multipleRoles: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_MULTIPLE_ROLES,
|
process.env.UNLEASH_EXPERIMENTAL_MULTIPLE_ROLES,
|
||||||
false,
|
false,
|
||||||
|
@ -39,7 +39,6 @@ process.nextTick(async () => {
|
|||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
slackAppAddon: true,
|
slackAppAddon: true,
|
||||||
lastSeenByEnvironment: true,
|
lastSeenByEnvironment: true,
|
||||||
integrationsRework: true,
|
|
||||||
featureNamingPattern: true,
|
featureNamingPattern: true,
|
||||||
doraMetrics: true,
|
doraMetrics: true,
|
||||||
variantTypeNumber: true,
|
variantTypeNumber: true,
|
||||||
|
@ -63,6 +63,7 @@ beforeAll(async () => {
|
|||||||
service = new PlaygroundService(config, {
|
service = new PlaygroundService(config, {
|
||||||
featureToggleServiceV2: featureToggleService,
|
featureToggleServiceV2: featureToggleService,
|
||||||
segmentService,
|
segmentService,
|
||||||
|
privateProjectChecker,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,3 +39,61 @@ GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
|
|||||||
```
|
```
|
||||||
|
|
||||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `TypeError: source_default(...).bold is not a function`
|
||||||
|
|
||||||
|
If you get an error like this, it's probably due to a formatting issue within one of the markdown files. It could be
|
||||||
|
|
||||||
|
- unescaped angle brackets (markdown will try to parse `<your-key>` (when it's not quoted) as HTML, which breaks the build)
|
||||||
|
- incorrectly formatted titles or missing pieces of files
|
||||||
|
- a lot of other stuff.
|
||||||
|
|
||||||
|
```console
|
||||||
|
Component Figure was not imported, exported, or provided by MDXProvider as global scope
|
||||||
|
|
||||||
|
TypeError: source_default(...).bold is not a function
|
||||||
|
[ERROR] Unable to build website for locale en.
|
||||||
|
```
|
||||||
|
|
||||||
|
This error is very hard to debug, but there is a trick that appears to work (as shared in [this discussion on docusaurus' repo](https://github.com/facebook/docusaurus/issues/7686#issuecomment-1486771382)):
|
||||||
|
|
||||||
|
In `node_modules/@docusaurus/core/lib/client/serverEntry.js`, remove all references to `chalk`. You can use a regex replace for that, by replacing `chalk(\w|\.)+` with the empty string.
|
||||||
|
|
||||||
|
Depending on your editor, that regex might need more escapes. For instance, here's a command to run with `evil-ex` in Emacs:
|
||||||
|
|
||||||
|
```
|
||||||
|
%s/chalk\(\w\|\.\)+//g
|
||||||
|
```
|
||||||
|
|
||||||
|
For macOS `sed`, it'd be:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sed -i '' 's/chalk\(\w\|\.\)\+//g' node_modules/@docusaurus/core/lib/client/serverEntry.js
|
||||||
|
```
|
||||||
|
|
||||||
|
For GNU `sed`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sed -i 's/chalk\(\w\|\.\)\+//g' node_modules/@docusaurus/core/lib/client/serverEntry.js
|
||||||
|
```
|
||||||
|
|
||||||
|
That might turn your error into something like this:
|
||||||
|
|
||||||
|
```console
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/change-requests.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/feature-types.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/frontend-api.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/maintenance.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/notifications.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/personal-access-tokens.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/segments.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/service-accounts.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/telemetry.
|
||||||
|
[ERROR] Docusaurus server-side rendering could not render static page with path /reference/api/unleash/unstable.
|
||||||
|
Component Figure was not imported, exported, or provided by MDXProvider as global scope
|
||||||
|
|
||||||
|
Error: Unexpected: cant find current sidebar in context
|
||||||
|
[ERROR] Unable to build website for locale en.
|
||||||
|
```
|
||||||
|
30
yarn.lock
30
yarn.lock
@ -761,7 +761,7 @@
|
|||||||
jest-util "^29.7.0"
|
jest-util "^29.7.0"
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
"@jest/core@^29.6.4", "@jest/core@^29.7.0":
|
"@jest/core@^29.7.0":
|
||||||
version "29.7.0"
|
version "29.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f"
|
resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f"
|
||||||
integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==
|
integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==
|
||||||
@ -3603,10 +3603,10 @@ faker@5.5.3:
|
|||||||
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
|
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
|
||||||
integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
|
integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
|
||||||
|
|
||||||
fast-check@3.12.1:
|
fast-check@3.13.0:
|
||||||
version "3.12.1"
|
version "3.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.12.1.tgz#00c35e83bcbec80fd35e0c204f9996309f004b3d"
|
resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.13.0.tgz#1099a36fd945fb06f6e68e261c91fa8c70c29802"
|
||||||
integrity sha512-aRN6WrO+q3TZV8CjXnpebx9bTHrpYIOVi6v3ttxciZiWqS739yy8b1oVx+qNEyV1b8RdVjlp/+miTY6yAp90HA==
|
integrity sha512-m6+3gZ/yTiCWTuV/1e/UuPPjyyyHdQ5gu0pMd84C6705VTDjAgAE6nqFT5jhgegFllCJ95yOzBpqvJSs2DZAxQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
pure-rand "^6.0.0"
|
pure-rand "^6.0.0"
|
||||||
|
|
||||||
@ -4761,7 +4761,7 @@ jest-circus@^29.7.0:
|
|||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
stack-utils "^2.0.3"
|
stack-utils "^2.0.3"
|
||||||
|
|
||||||
jest-cli@^29.6.4:
|
jest-cli@^29.7.0:
|
||||||
version "29.7.0"
|
version "29.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995"
|
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995"
|
||||||
integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==
|
integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==
|
||||||
@ -5136,15 +5136,15 @@ jest-worker@^29.7.0:
|
|||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^8.0.0"
|
supports-color "^8.0.0"
|
||||||
|
|
||||||
jest@29.6.4:
|
jest@29.7.0:
|
||||||
version "29.6.4"
|
version "29.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.4.tgz#7c48e67a445ba264b778253b5d78d4ebc9d0a622"
|
resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613"
|
||||||
integrity sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==
|
integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jest/core" "^29.6.4"
|
"@jest/core" "^29.7.0"
|
||||||
"@jest/types" "^29.6.3"
|
"@jest/types" "^29.6.3"
|
||||||
import-local "^3.0.2"
|
import-local "^3.0.2"
|
||||||
jest-cli "^29.6.4"
|
jest-cli "^29.7.0"
|
||||||
|
|
||||||
joi@^17.3.0, joi@^17.7.0:
|
joi@^17.3.0, joi@^17.7.0:
|
||||||
version "17.10.1"
|
version "17.10.1"
|
||||||
@ -7962,9 +7962,9 @@ uuid@^8.3.2:
|
|||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
uuid@^9.0.0:
|
uuid@^9.0.0:
|
||||||
version "9.0.0"
|
version "9.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
|
||||||
|
|
||||||
v8-compile-cache-lib@^3.0.1:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user