1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

Integrations - frontend adjustments (#4527)

## About the changes
  - [x] Create a feature flag
  - [x] Rename page title
  - [x] Rename menu item
  - [x] Update frontend URL (add redirect from old one)


https://linear.app/unleash/issue/1-1263/integrations-frontend-adjustments
This commit is contained in:
Tymoteusz Czech 2023-08-22 14:40:38 +02:00 committed by GitHub
parent 8a3889d570
commit 573518e48d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 225 additions and 94 deletions

View File

@ -1,19 +0,0 @@
import React from 'react';
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';
export const AddonList = () => {
const { providers, addons, loading } = useAddons();
return (
<>
<ConditionallyRender
condition={addons.length > 0}
show={<ConfiguredAddons />}
/>
<AvailableAddons loading={loading} providers={providers} />
</>
);
};

View File

@ -0,0 +1,19 @@
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
export const AddonRedirect = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
navigate(`/integrations${location.pathname.replace('/addons', '')}`);
}, [location.pathname, navigate]);
return (
<PageContent>
Addons where renamed to{' '}
<Link to="/integrations">/integrations</Link>
</PageContent>
);
};

View File

@ -1,5 +1,5 @@
import useAddons from 'hooks/api/getters/useAddons/useAddons'; import useAddons from 'hooks/api/getters/useAddons/useAddons';
import { AddonForm } from '../AddonForm/AddonForm'; import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
import { IAddon } from 'interfaces/addons'; import { IAddon } from 'interfaces/addons';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@ -14,7 +14,7 @@ export const DEFAULT_DATA = {
environments: [], environments: [],
} as unknown as IAddon; // TODO: improve type } as unknown as IAddon; // TODO: improve type
export const CreateAddon = () => { export const CreateIntegration = () => {
const providerId = useRequiredPathParam('providerId'); const providerId = useRequiredPathParam('providerId');
const { providers, refetchAddons } = useAddons(); const { providers, refetchAddons } = useAddons();
@ -29,7 +29,7 @@ export const CreateAddon = () => {
}; };
return ( return (
<AddonForm <IntegrationForm
editMode={editMode} editMode={editMode}
provider={provider} provider={provider}
fetch={refetchAddons} fetch={refetchAddons}

View File

@ -1,11 +1,11 @@
import useAddons from 'hooks/api/getters/useAddons/useAddons'; import useAddons from 'hooks/api/getters/useAddons/useAddons';
import { AddonForm } from '../AddonForm/AddonForm'; import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
import { IAddon } from 'interfaces/addons'; import { IAddon } from 'interfaces/addons';
import { DEFAULT_DATA } from '../CreateAddon/CreateAddon'; import { DEFAULT_DATA } from '../CreateIntegration/CreateIntegration';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const EditAddon = () => { export const EditIntegration = () => {
const addonId = useRequiredPathParam('addonId'); const addonId = useRequiredPathParam('addonId');
const { providers, addons, refetchAddons } = useAddons(); const { providers, addons, refetchAddons } = useAddons();
@ -18,7 +18,7 @@ export const EditAddon = () => {
: undefined; : undefined;
return ( return (
<AddonForm <IntegrationForm
editMode={editMode} editMode={editMode}
provider={provider} provider={provider}
fetch={refetchAddons} fetch={refetchAddons}

View File

@ -16,8 +16,8 @@ import {
import produce from 'immer'; import produce from 'immer';
import { trim } from 'component/common/util'; import { trim } from 'component/common/util';
import { IAddon, IAddonProvider } from 'interfaces/addons'; import { IAddon, IAddonProvider } from 'interfaces/addons';
import { AddonParameters } from './AddonParameters/AddonParameters'; import { IntegrationParameters } from './IntegrationParameters/IntegrationParameters';
import { AddonInstall } from './AddonInstall/AddonInstall'; import { IntegrationInstall } from './IntegrationInstall/IntegrationInstall';
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
@ -25,7 +25,7 @@ import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import { AddonMultiSelector } from './AddonMultiSelector/AddonMultiSelector'; import { IntegrationMultiSelector } from './IntegrationMultiSelector/IntegrationMultiSelector';
import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import PermissionButton from 'component/common/PermissionButton/PermissionButton';
@ -42,7 +42,7 @@ import {
StyledContainer, StyledContainer,
StyledButtonContainer, StyledButtonContainer,
StyledButtonSection, StyledButtonSection,
} from './AddonForm.styles'; } from './IntegrationForm.styles';
import { useTheme } from '@mui/system'; import { useTheme } from '@mui/system';
import { GO_BACK } from 'constants/navigate'; import { GO_BACK } from 'constants/navigate';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -54,7 +54,7 @@ interface IAddonFormProps {
editMode: boolean; editMode: boolean;
} }
export const AddonForm: VFC<IAddonFormProps> = ({ export const IntegrationForm: VFC<IAddonFormProps> = ({
editMode, editMode,
provider, provider,
addon: initialValues, addon: initialValues,
@ -272,7 +272,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
<ConditionallyRender <ConditionallyRender
condition={Boolean(installation)} condition={Boolean(installation)}
show={() => ( show={() => (
<AddonInstall <IntegrationInstall
url={installation!.url} url={installation!.url}
title={installation!.title} title={installation!.title}
helpText={installation!.helpText} helpText={installation!.helpText}
@ -321,7 +321,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
</StyledFormSection> </StyledFormSection>
<StyledFormSection> <StyledFormSection>
<AddonMultiSelector <IntegrationMultiSelector
options={selectableEvents || []} options={selectableEvents || []}
selectedItems={formValues.events} selectedItems={formValues.events}
onChange={setEventValues} onChange={setEventValues}
@ -333,7 +333,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
/> />
</StyledFormSection> </StyledFormSection>
<StyledFormSection> <StyledFormSection>
<AddonMultiSelector <IntegrationMultiSelector
options={selectableProjects} options={selectableProjects}
selectedItems={formValues.projects || []} selectedItems={formValues.projects || []}
onChange={setProjects} onChange={setProjects}
@ -342,7 +342,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
/> />
</StyledFormSection> </StyledFormSection>
<StyledFormSection> <StyledFormSection>
<AddonMultiSelector <IntegrationMultiSelector
options={selectableEnvironments} options={selectableEnvironments}
selectedItems={formValues.environments || []} selectedItems={formValues.environments || []}
onChange={setEnvironments} onChange={setEnvironments}
@ -351,7 +351,7 @@ export const AddonForm: VFC<IAddonFormProps> = ({
/> />
</StyledFormSection> </StyledFormSection>
<StyledFormSection> <StyledFormSection>
<AddonParameters <IntegrationParameters
provider={provider} provider={provider}
config={formValues} config={formValues}
parametersErrors={errors.parameters} parametersErrors={errors.parameters}

View File

@ -3,7 +3,7 @@ import {
StyledFormSection, StyledFormSection,
StyledHelpText, StyledHelpText,
StyledTitle, StyledTitle,
} from '../AddonForm.styles'; } from '../IntegrationForm.styles';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -13,7 +13,7 @@ export interface IAddonInstallProps {
helpText?: string; helpText?: string;
} }
export const AddonInstall = ({ export const IntegrationInstall = ({
url, url,
title = 'Install addon', title = 'Install addon',
helpText = 'Click this button to install this addon.', helpText = 'Click this button to install this addon.',

View File

@ -4,15 +4,15 @@ import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { render } from 'utils/testRenderer'; import { render } from 'utils/testRenderer';
import { import {
IAddonMultiSelectorProps, IIntegrationMultiSelectorProps,
AddonMultiSelector, IntegrationMultiSelector,
} from './AddonMultiSelector'; } from './IntegrationMultiSelector';
import { testServerRoute, testServerSetup } from 'utils/testServer'; import { testServerRoute, testServerSetup } from 'utils/testServer';
const onChange = vi.fn(); const onChange = vi.fn();
const onFocus = vi.fn(); const onFocus = vi.fn();
const mockProps: IAddonMultiSelectorProps = { const mockProps: IIntegrationMultiSelectorProps = {
options: [ options: [
{ label: 'Project1', value: 'project1' }, { label: 'Project1', value: 'project1' },
{ label: 'Project2', value: 'project2' }, { label: 'Project2', value: 'project2' },
@ -35,7 +35,9 @@ describe('AddonMultiSelector', () => {
}); });
it('renders with default state', () => { it('renders with default state', () => {
render(<AddonMultiSelector {...mockProps} selectedItems={['*']} />); render(
<IntegrationMultiSelector {...mockProps} selectedItems={['*']} />
);
const checkbox = screen.getByLabelText( const checkbox = screen.getByLabelText(
/all current and future projects/i /all current and future projects/i
@ -49,7 +51,9 @@ describe('AddonMultiSelector', () => {
it('can toggle "ALL" checkbox', async () => { it('can toggle "ALL" checkbox', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
render(<AddonMultiSelector {...mockProps} selectedItems={['*']} />); render(
<IntegrationMultiSelector {...mockProps} selectedItems={['*']} />
);
await user.click(screen.getByTestId('select-all-projects')); await user.click(screen.getByTestId('select-all-projects'));
@ -70,7 +74,10 @@ describe('AddonMultiSelector', () => {
it('renders with autocomplete enabled if default value is not a wildcard', () => { it('renders with autocomplete enabled if default value is not a wildcard', () => {
render( render(
<AddonMultiSelector {...mockProps} selectedItems={['project1']} /> <IntegrationMultiSelector
{...mockProps}
selectedItems={['project1']}
/>
); );
const checkbox = screen.getByLabelText( const checkbox = screen.getByLabelText(
@ -87,7 +94,7 @@ describe('AddonMultiSelector', () => {
it("doesn't show up for less than 3 options", async () => { it("doesn't show up for less than 3 options", async () => {
const user = userEvent.setup(); const user = userEvent.setup();
render( render(
<AddonMultiSelector <IntegrationMultiSelector
{...mockProps} {...mockProps}
selectedItems={[]} selectedItems={[]}
options={[ options={[
@ -108,7 +115,7 @@ describe('AddonMultiSelector', () => {
it('can filter options', async () => { it('can filter options', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
render( render(
<AddonMultiSelector <IntegrationMultiSelector
{...mockProps} {...mockProps}
selectedItems={[]} selectedItems={[]}
options={[ options={[

View File

@ -22,9 +22,9 @@ import {
StyledHelpText, StyledHelpText,
StyledSelectAllFormControlLabel, StyledSelectAllFormControlLabel,
StyledTitle, StyledTitle,
} from '../AddonForm.styles'; } from '../IntegrationForm.styles';
export interface IAddonMultiSelectorProps { export interface IIntegrationMultiSelectorProps {
options: IAutocompleteBoxOption[]; options: IAutocompleteBoxOption[];
selectedItems: string[]; selectedItems: string[];
onChange: (value: string[]) => void; onChange: (value: string[]) => void;
@ -44,7 +44,7 @@ const StyledCheckbox = styled(Checkbox)(() => ({
const CustomPaper = ({ ...props }) => <Paper elevation={8} {...props} />; const CustomPaper = ({ ...props }) => <Paper elevation={8} {...props} />;
export const AddonMultiSelector: VFC<IAddonMultiSelectorProps> = ({ export const IntegrationMultiSelector: VFC<IIntegrationMultiSelectorProps> = ({
options, options,
selectedItems, selectedItems,
onChange, onChange,
@ -138,9 +138,9 @@ export const AddonMultiSelector: VFC<IAddonMultiSelectorProps> = ({
const DefaultHelpText = () => ( const DefaultHelpText = () => (
<StyledHelpText> <StyledHelpText>
Selecting {entityName}(s) here will filter events so that your addon Selecting {entityName}(s) here will filter events so that your
will only receive events that are tagged with one of your{' '} integration will only receive events that are tagged with one of
{entityName}s. your {entityName}s.
</StyledHelpText> </StyledHelpText>
); );

View File

@ -1,7 +1,7 @@
import { TextField, Typography } from '@mui/material'; import { TextField, Typography } from '@mui/material';
import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons'; import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons';
import { ChangeEventHandler } from 'react'; import { ChangeEventHandler } from 'react';
import { StyledAddonParameterContainer } from '../../AddonForm.styles'; import { StyledAddonParameterContainer } from '../../IntegrationForm.styles';
const resolveType = ({ type = 'text', sensitive = false }, value: string) => { const resolveType = ({ type = 'text', sensitive = false }, value: string) => {
if (sensitive && value === MASKED_VALUE) { if (sensitive && value === MASKED_VALUE) {
@ -15,19 +15,19 @@ const resolveType = ({ type = 'text', sensitive = false }, value: string) => {
const MASKED_VALUE = '*****'; const MASKED_VALUE = '*****';
export interface IAddonParameterProps { export interface IIntegrationParameterProps {
parametersErrors: Record<string, string>; parametersErrors: Record<string, string>;
definition: IAddonProviderParams; definition: IAddonProviderParams;
setParameterValue: (param: string) => ChangeEventHandler<HTMLInputElement>; setParameterValue: (param: string) => ChangeEventHandler<HTMLInputElement>;
config: IAddonConfig; config: IAddonConfig;
} }
export const AddonParameter = ({ export const IntegrationParameter = ({
definition, definition,
config, config,
parametersErrors, parametersErrors,
setParameterValue, setParameterValue,
}: IAddonParameterProps) => { }: IIntegrationParameterProps) => {
const value = config.parameters[definition.name] || ''; const value = config.parameters[definition.name] || '';
const type = resolveType(definition, value); const type = resolveType(definition, value);
const error = parametersErrors[definition.name]; const error = parametersErrors[definition.name];

View File

@ -1,26 +1,26 @@
import React from 'react'; import React from 'react';
import { IAddonProvider } from 'interfaces/addons'; import { IAddonProvider } from 'interfaces/addons';
import { import {
AddonParameter, IntegrationParameter,
IAddonParameterProps, IIntegrationParameterProps,
} from './AddonParameter/AddonParameter'; } from './IntegrationParameter/IntegrationParameter';
import { StyledTitle } from '../AddonForm.styles'; import { StyledTitle } from '../IntegrationForm.styles';
interface IAddonParametersProps { interface IIntegrationParametersProps {
provider?: IAddonProvider; provider?: IAddonProvider;
parametersErrors: IAddonParameterProps['parametersErrors']; parametersErrors: IIntegrationParameterProps['parametersErrors'];
editMode: boolean; editMode: boolean;
setParameterValue: IAddonParameterProps['setParameterValue']; setParameterValue: IIntegrationParameterProps['setParameterValue'];
config: IAddonParameterProps['config']; config: IIntegrationParameterProps['config'];
} }
export const AddonParameters = ({ export const IntegrationParameters = ({
provider, provider,
config, config,
parametersErrors, parametersErrors,
setParameterValue, setParameterValue,
editMode, editMode,
}: IAddonParametersProps) => { }: IIntegrationParametersProps) => {
if (!provider) return null; if (!provider) return null;
return ( return (
<React.Fragment> <React.Fragment>
@ -33,7 +33,7 @@ export const AddonParameters = ({
</p> </p>
) : null} ) : null}
{provider.parameters.map(parameter => ( {provider.parameters.map(parameter => (
<AddonParameter <IntegrationParameter
key={parameter.name} key={parameter.name}
definition={parameter} definition={parameter}
parametersErrors={parametersErrors} parametersErrors={parametersErrors}

View File

@ -16,10 +16,11 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { sortTypes } from 'utils/sortTypes'; import { sortTypes } from 'utils/sortTypes';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { ConfigureAddonButton } from './ConfigureAddonButton/ConfigureAddonButton'; import { ConfigureAddonsButton } from './ConfigureAddonButton/ConfigureAddonsButton';
import { AddonIcon } from '../AddonIcon/AddonIcon'; import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
import { AddonNameCell } from '../AddonNameCell/AddonNameCell'; import { IntegrationNameCell } from '../IntegrationNameCell/IntegrationNameCell';
import { IAddonInstallation } from 'interfaces/addons'; import { IAddonInstallation } from 'interfaces/addons';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
interface IProvider { interface IProvider {
name: string; name: string;
@ -37,6 +38,9 @@ interface IAvailableAddonsProps {
loading: boolean; loading: boolean;
} }
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const AvailableAddons = ({ export const AvailableAddons = ({
providers, providers,
loading, loading,
@ -70,7 +74,9 @@ export const AvailableAddons = ({
}, },
}: any) => { }: any) => {
return ( return (
<IconCell icon={<AddonIcon name={name as string} />} /> <IconCell
icon={<IntegrationIcon name={name as string} />}
/>
); );
}, },
}, },
@ -79,7 +85,7 @@ export const AvailableAddons = ({
accessor: 'name', accessor: 'name',
width: '90%', width: '90%',
Cell: ({ row: { original } }: any) => ( Cell: ({ row: { original } }: any) => (
<AddonNameCell provider={original} /> <IntegrationNameCell provider={original} />
), ),
sortType: 'alphanumeric', sortType: 'alphanumeric',
}, },
@ -88,7 +94,7 @@ export const AvailableAddons = ({
align: 'center', align: 'center',
Cell: ({ row: { original } }: any) => ( Cell: ({ row: { original } }: any) => (
<ActionCell> <ActionCell>
<ConfigureAddonButton provider={original} /> <ConfigureAddonsButton provider={original} />
</ActionCell> </ActionCell>
), ),
width: 150, width: 150,

View File

@ -3,13 +3,16 @@ import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
import { IAddonProvider } from 'interfaces/addons'; import { IAddonProvider } from 'interfaces/addons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
interface IConfigureAddonButtonProps { interface IConfigureAddonsButtonProps {
provider: IAddonProvider; provider: IAddonProvider;
} }
export const ConfigureAddonButton = ({ /**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfigureAddonsButton = ({
provider, provider,
}: IConfigureAddonButtonProps) => { }: IConfigureAddonsButtonProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (

View File

@ -0,0 +1,8 @@
import { PageContent } from 'component/common/PageContent/PageContent';
import { VFC } from 'react';
interface IAvailableIntegrationsProps {}
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = () => {
return <PageContent>Available integrations</PageContent>;
};

View File

@ -13,10 +13,13 @@ import { useTable, useSortBy } from 'react-table';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { SortableTableHeader, TablePlaceholder } from 'component/common/Table'; import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { AddonIcon } from '../AddonIcon/AddonIcon'; import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell'; import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
import { AddonNameCell } from '../AddonNameCell/AddonNameCell'; import { IntegrationNameCell } from '../IntegrationNameCell/IntegrationNameCell';
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfiguredAddons = () => { export const ConfiguredAddons = () => {
const { refetchAddons, addons, providers, loading } = useAddons(); const { refetchAddons, addons, providers, loading } = useAddons();
const { updateAddon, removeAddon } = useAddonsApi(); const { updateAddon, removeAddon } = useAddonsApi();
@ -73,7 +76,9 @@ export const ConfiguredAddons = () => {
original: { provider }, original: { provider },
}, },
}: any) => ( }: any) => (
<IconCell icon={<AddonIcon name={provider as string} />} /> <IconCell
icon={<IntegrationIcon name={provider as string} />}
/>
), ),
disableSortBy: true, disableSortBy: true,
}, },
@ -86,7 +91,7 @@ export const ConfiguredAddons = () => {
original: { provider, description }, original: { provider, description },
}, },
}: any) => ( }: any) => (
<AddonNameCell <IntegrationNameCell
provider={{ provider={{
...(providers.find( ...(providers.find(
({ name }) => name === provider ({ name }) => name === provider

View File

@ -18,6 +18,9 @@ interface IConfiguredAddonsActionsCellProps {
setDeletedAddon: React.Dispatch<React.SetStateAction<IAddon>>; setDeletedAddon: React.Dispatch<React.SetStateAction<IAddon>>;
} }
/**
* @deprecated Remove when integrationsRework flag is removed
*/
export const ConfiguredAddonsActionsCell = ({ export const ConfiguredAddonsActionsCell = ({
toggleAddon, toggleAddon,
setShowDelete, setShowDelete,

View File

@ -14,11 +14,11 @@ const style: React.CSSProperties = {
marginRight: '16px', marginRight: '16px',
}; };
interface IAddonIconProps { interface IIntegrationIconProps {
name: string; name: string;
} }
export const AddonIcon = ({ name }: IAddonIconProps) => { export const IntegrationIcon = ({ name }: IIntegrationIconProps) => {
switch (name) { switch (name) {
case 'slack': case 'slack':
case 'slack-app': case 'slack-app':

View File

@ -0,0 +1,28 @@
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';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
export const IntegrationList = () => {
const { providers, addons, loading } = useAddons();
const { uiConfig } = useUiConfig();
const integrationsRework = uiConfig?.flags?.integrationsRework || false;
return (
<>
<ConditionallyRender
condition={addons.length > 0}
show={<ConfiguredAddons />}
/>
<ConditionallyRender
condition={integrationsRework}
show={<AvailableIntegrations />}
elseShow={
<AvailableAddons loading={loading} providers={providers} />
}
/>
</>
);
};

View File

@ -10,14 +10,19 @@ const StyledBadge = styled(Badge)(({ theme }) => ({
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
})); }));
interface IAddonNameCellProps { interface IIntegrationNameCellProps {
provider: Pick< provider: Pick<
IAddonProvider, IAddonProvider,
'displayName' | 'description' | 'deprecated' 'displayName' | 'description' | 'deprecated'
>; >;
} }
export const AddonNameCell = ({ provider }: IAddonNameCellProps) => ( /**
* @deprecated Remove when integrationsRework flag is removed
*/
export const IntegrationNameCell = ({
provider,
}: IIntegrationNameCellProps) => (
<HighlightCell <HighlightCell
value={provider.displayName} value={provider.displayName}
subtitle={provider.description} subtitle={provider.description}

View File

@ -317,6 +317,34 @@ exports[`returns all baseRoutes 1`] = `
"title": "Addons", "title": "Addons",
"type": "protected", "type": "protected",
}, },
{
"component": [Function],
"menu": {},
"parent": "/integrations",
"path": "/integrations/create/:providerId",
"title": "Create",
"type": "protected",
},
{
"component": [Function],
"menu": {},
"parent": "/integrations",
"path": "/integrations/edit/:addonId",
"title": "Edit",
"type": "protected",
},
{
"component": [Function],
"flag": "integrationsRework",
"hidden": false,
"menu": {
"advanced": true,
"mobile": true,
},
"path": "/integrations",
"title": "Integrations",
"type": "protected",
},
{ {
"component": [Function], "component": [Function],
"flag": "SE", "flag": "SE",

View File

@ -2,7 +2,7 @@ import { FeatureToggleListTable } from 'component/feature/FeatureToggleList/Feat
import { StrategyView } from 'component/strategies/StrategyView/StrategyView'; import { StrategyView } from 'component/strategies/StrategyView/StrategyView';
import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList'; import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList';
import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList'; import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList';
import { AddonList } from 'component/addons/AddonList/AddonList'; import { IntegrationList } from 'component/integrations/IntegrationList/IntegrationList';
import Login from 'component/user/Login/Login'; import Login from 'component/user/Login/Login';
import { EEA, P, SE } from 'component/common/flags'; import { EEA, P, SE } from 'component/common/flags';
import { NewUser } from 'component/user/NewUser/NewUser'; import { NewUser } from 'component/user/NewUser/NewUser';
@ -21,8 +21,8 @@ import EditFeature from 'component/feature/EditFeature/EditFeature';
import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit'; import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit';
import ContextList from 'component/context/ContextList/ContextList/ContextList'; import ContextList from 'component/context/ContextList/ContextList/ContextList';
import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView'; import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView';
import { CreateAddon } from 'component/addons/CreateAddon/CreateAddon'; import { CreateIntegration } from 'component/integrations/CreateIntegration/CreateIntegration';
import { EditAddon } from 'component/addons/EditAddon/EditAddon'; import { EditIntegration } from 'component/integrations/EditIntegration/EditIntegration';
import { CopyFeatureToggle } from 'component/feature/CopyFeature/CopyFeature'; import { CopyFeatureToggle } from 'component/feature/CopyFeature/CopyFeature';
import { EventPage } from 'component/events/EventPage/EventPage'; import { EventPage } from 'component/events/EventPage/EventPage';
import { CreateStrategy } from 'component/strategies/CreateStrategy/CreateStrategy'; import { CreateStrategy } from 'component/strategies/CreateStrategy/CreateStrategy';
@ -300,12 +300,13 @@ export const routes: IRoute[] = [
menu: { mobile: true, advanced: true }, menu: { mobile: true, advanced: true },
}, },
// Addons // Integrations
{ {
path: '/addons/create/:providerId', path: '/addons/create/:providerId',
parent: '/addons', parent: '/addons',
title: 'Create', title: 'Create',
component: CreateAddon, component: CreateIntegration,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
type: 'protected', type: 'protected',
menu: {}, menu: {},
}, },
@ -313,17 +314,45 @@ export const routes: IRoute[] = [
path: '/addons/edit/:addonId', path: '/addons/edit/:addonId',
parent: '/addons', parent: '/addons',
title: 'Edit', title: 'Edit',
component: EditAddon, component: EditIntegration,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
type: 'protected', type: 'protected',
menu: {}, menu: {},
}, },
{ {
path: '/addons', path: '/addons',
title: 'Addons', title: 'Addons',
component: AddonList, component: IntegrationList,
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
hidden: false, hidden: false,
type: 'protected', type: 'protected',
menu: { mobile: true, advanced: true }, menu: { mobile: true, advanced: true },
// TODO: remove 'addons' from menu after removing `integrationsRework` menu flag
},
{
path: '/integrations/create/:providerId',
parent: '/integrations',
title: 'Create',
component: CreateIntegration,
type: 'protected',
menu: {},
},
{
path: '/integrations/edit/:addonId',
parent: '/integrations',
title: 'Edit',
component: EditIntegration,
type: 'protected',
menu: {},
},
{
path: '/integrations',
title: 'Integrations',
component: IntegrationList,
hidden: false,
type: 'protected',
menu: { mobile: true, advanced: true },
flag: 'integrationsRework',
}, },
// Segments // Segments

View File

@ -58,6 +58,7 @@ export interface IFlags {
changeRequestReject?: boolean; changeRequestReject?: boolean;
lastSeenByEnvironment?: boolean; lastSeenByEnvironment?: boolean;
newApplicationList?: boolean; newApplicationList?: boolean;
integrationsRework?: boolean;
} }
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -82,6 +82,7 @@ exports[`should create default config 1`] = `
"filterInvalidClientMetrics": false, "filterInvalidClientMetrics": false,
"frontendNavigationUpdate": false, "frontendNavigationUpdate": false,
"googleAuthEnabled": false, "googleAuthEnabled": false,
"integrationsRework": false,
"lastSeenByEnvironment": false, "lastSeenByEnvironment": false,
"maintenanceMode": false, "maintenanceMode": false,
"messageBanner": { "messageBanner": {
@ -118,6 +119,7 @@ exports[`should create default config 1`] = `
"filterInvalidClientMetrics": false, "filterInvalidClientMetrics": false,
"frontendNavigationUpdate": false, "frontendNavigationUpdate": false,
"googleAuthEnabled": false, "googleAuthEnabled": false,
"integrationsRework": false,
"lastSeenByEnvironment": false, "lastSeenByEnvironment": false,
"maintenanceMode": false, "maintenanceMode": false,
"messageBanner": { "messageBanner": {

View File

@ -29,7 +29,8 @@ export type IFlagKey =
| 'segmentChangeRequests' | 'segmentChangeRequests'
| 'changeRequestReject' | 'changeRequestReject'
| 'customRootRolesKillSwitch' | 'customRootRolesKillSwitch'
| 'newApplicationList'; | 'newApplicationList'
| 'integrationsRework';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -133,6 +134,10 @@ 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,
),
}; };
export const defaultExperimentalOptions: IExperimentalOptions = { export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -13,6 +13,7 @@ yarn install
```console ```console
yarn generate yarn generate
``` ```
Generate the Open API docs that live at Reference documentation > APIs > OpenAPI Generate the Open API docs that live at Reference documentation > APIs > OpenAPI
## Local Development ## Local Development
@ -21,7 +22,7 @@ Generate the Open API docs that live at Reference documentation > APIs > OpenAPI
yarn start yarn start
``` ```
Start a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. Start a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build ## Build