mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
Integration card component (#4557)
 ## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> <!-- Does it close an issue? Multiple? --> Closes # <!-- (For internal contributors): Does it relate to an issue on public roadmap? --> <!-- Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: # --> ### Important files <!-- PRs can contain a lot of changes, but not all changes are equally important. Where should a reviewer start looking to get an overview of the changes? Are any files particularly important? --> ## Discussion points <!-- Anything about the PR you'd like to discuss before it gets merged? Got any questions or doubts? -->
This commit is contained in:
parent
a1e98056ec
commit
47a59224bb
@ -1,10 +1,10 @@
|
|||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
|
import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import { IAddon } from 'interfaces/addons';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
export const DEFAULT_DATA = {
|
export const DEFAULT_DATA: Omit<AddonSchema, 'id'> = {
|
||||||
provider: '',
|
provider: '',
|
||||||
description: '',
|
description: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -12,7 +12,7 @@ export const DEFAULT_DATA = {
|
|||||||
events: [],
|
events: [],
|
||||||
projects: [],
|
projects: [],
|
||||||
environments: [],
|
environments: [],
|
||||||
} as unknown as IAddon; // TODO: improve type
|
};
|
||||||
|
|
||||||
export const CreateIntegration = () => {
|
export const CreateIntegration = () => {
|
||||||
const providerId = useRequiredPathParam('providerId');
|
const providerId = useRequiredPathParam('providerId');
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
|
import { IntegrationForm } from '../IntegrationForm/IntegrationForm';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import { IAddon } from 'interfaces/addons';
|
|
||||||
import { DEFAULT_DATA } from '../CreateIntegration/CreateIntegration';
|
import { DEFAULT_DATA } from '../CreateIntegration/CreateIntegration';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
export const EditIntegration = () => {
|
export const EditIntegration = () => {
|
||||||
const addonId = useRequiredPathParam('addonId');
|
const addonId = useRequiredPathParam('addonId');
|
||||||
@ -11,7 +11,7 @@ export const EditIntegration = () => {
|
|||||||
|
|
||||||
const editMode = true;
|
const editMode = true;
|
||||||
const addon = addons.find(
|
const addon = addons.find(
|
||||||
(addon: IAddon) => addon.id === Number(addonId)
|
(addon: AddonSchema) => addon.id === Number(addonId)
|
||||||
) || { ...cloneDeep(DEFAULT_DATA) };
|
) || { ...cloneDeep(DEFAULT_DATA) };
|
||||||
const provider = addon
|
const provider = addon
|
||||||
? providers.find(provider => provider.name === addon.provider)
|
? providers.find(provider => provider.name === addon.provider)
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
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 type { AddonSchema, AddonTypeSchema } from 'openapi';
|
||||||
import { IntegrationParameters } from './IntegrationParameters/IntegrationParameters';
|
import { IntegrationParameters } from './IntegrationParameters/IntegrationParameters';
|
||||||
import { IntegrationInstall } from './IntegrationInstall/IntegrationInstall';
|
import { IntegrationInstall } from './IntegrationInstall/IntegrationInstall';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
@ -49,14 +49,14 @@ import { GO_BACK } from 'constants/navigate';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { IntegrationDeleteDialog } from './IntegrationDeleteDialog/IntegrationDeleteDialog';
|
import { IntegrationDeleteDialog } from './IntegrationDeleteDialog/IntegrationDeleteDialog';
|
||||||
|
|
||||||
interface IAddonFormProps {
|
type IntegrationFormProps = {
|
||||||
provider?: IAddonProvider;
|
provider?: AddonTypeSchema;
|
||||||
addon: IAddon;
|
|
||||||
fetch: () => void;
|
fetch: () => void;
|
||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
}
|
addon: AddonSchema | Omit<AddonSchema, 'id'>;
|
||||||
|
};
|
||||||
|
|
||||||
export const IntegrationForm: VFC<IAddonFormProps> = ({
|
export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
||||||
editMode,
|
editMode,
|
||||||
provider,
|
provider,
|
||||||
addon: initialValues,
|
addon: initialValues,
|
||||||
@ -77,7 +77,7 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
value: environment.name,
|
value: environment.name,
|
||||||
label: environment.name,
|
label: environment.name,
|
||||||
}));
|
}));
|
||||||
const selectableEvents = provider?.events.map(event => ({
|
const selectableEvents = provider?.events?.map(event => ({
|
||||||
value: event,
|
value: event,
|
||||||
label: event,
|
label: event,
|
||||||
}));
|
}));
|
||||||
@ -97,7 +97,7 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
});
|
});
|
||||||
const submitText = editMode ? 'Update' : 'Create';
|
const submitText = editMode ? 'Update' : 'Create';
|
||||||
let url = `${uiConfig.unleashUrl}/api/admin/addons${
|
let url = `${uiConfig.unleashUrl}/api/admin/addons${
|
||||||
editMode ? `/${formValues.id}` : ``
|
editMode ? `/${(formValues as AddonSchema).id}` : ``
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
@ -207,8 +207,9 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
updatedErrors.containsErrors = true;
|
updatedErrors.containsErrors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.parameters.forEach(parameterConfig => {
|
provider.parameters?.forEach(parameterConfig => {
|
||||||
const value = trim(formValues.parameters[parameterConfig.name]);
|
let value = formValues.parameters[parameterConfig.name];
|
||||||
|
value = typeof value === 'string' ? trim(value) : value;
|
||||||
if (parameterConfig.required && !value) {
|
if (parameterConfig.required && !value) {
|
||||||
updatedErrors.parameters[parameterConfig.name] =
|
updatedErrors.parameters[parameterConfig.name] =
|
||||||
'This field is required';
|
'This field is required';
|
||||||
@ -223,14 +224,14 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
await updateAddon(formValues);
|
await updateAddon(formValues as AddonSchema);
|
||||||
navigate('/addons');
|
navigate('/addons');
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Addon updated successfully',
|
title: 'Addon updated successfully',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await createAddon(formValues);
|
await createAddon(formValues as Omit<AddonSchema, 'id'>);
|
||||||
navigate('/addons');
|
navigate('/addons');
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -255,7 +256,7 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
documentationUrl = 'https://unleash.github.io/docs/addons',
|
documentationUrl = 'https://unleash.github.io/docs/addons',
|
||||||
installation,
|
installation,
|
||||||
alerts,
|
alerts,
|
||||||
} = provider ? provider : ({} as Partial<IAddonProvider>);
|
} = provider ? provider : ({} as Partial<AddonTypeSchema>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
@ -356,7 +357,7 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
<StyledFormSection>
|
<StyledFormSection>
|
||||||
<IntegrationParameters
|
<IntegrationParameters
|
||||||
provider={provider}
|
provider={provider}
|
||||||
config={formValues}
|
config={formValues as AddonSchema}
|
||||||
parametersErrors={errors.parameters}
|
parametersErrors={errors.parameters}
|
||||||
editMode={editMode}
|
editMode={editMode}
|
||||||
setParameterValue={setParameterValue}
|
setParameterValue={setParameterValue}
|
||||||
@ -396,7 +397,7 @@ export const IntegrationForm: VFC<IAddonFormProps> = ({
|
|||||||
Delete
|
Delete
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
<IntegrationDeleteDialog
|
<IntegrationDeleteDialog
|
||||||
id={formValues.id}
|
id={(formValues as AddonSchema).id}
|
||||||
isOpen={isDeleteOpen}
|
isOpen={isDeleteOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setDeleteOpen(false);
|
setDeleteOpen(false);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { TextField, Typography } from '@mui/material';
|
import { TextField, Typography } from '@mui/material';
|
||||||
import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons';
|
|
||||||
import { ChangeEventHandler } from 'react';
|
import { ChangeEventHandler } from 'react';
|
||||||
import { StyledAddonParameterContainer } from '../../IntegrationForm.styles';
|
import { StyledAddonParameterContainer } from '../../IntegrationForm.styles';
|
||||||
|
import type { AddonParameterSchema, AddonSchema } from 'openapi';
|
||||||
|
|
||||||
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) {
|
||||||
@ -17,9 +17,9 @@ const MASKED_VALUE = '*****';
|
|||||||
|
|
||||||
export interface IIntegrationParameterProps {
|
export interface IIntegrationParameterProps {
|
||||||
parametersErrors: Record<string, string>;
|
parametersErrors: Record<string, string>;
|
||||||
definition: IAddonProviderParams;
|
definition: AddonParameterSchema;
|
||||||
setParameterValue: (param: string) => ChangeEventHandler<HTMLInputElement>;
|
setParameterValue: (param: string) => ChangeEventHandler<HTMLInputElement>;
|
||||||
config: IAddonConfig;
|
config: AddonSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IntegrationParameter = ({
|
export const IntegrationParameter = ({
|
||||||
@ -28,8 +28,11 @@ export const IntegrationParameter = ({
|
|||||||
parametersErrors,
|
parametersErrors,
|
||||||
setParameterValue,
|
setParameterValue,
|
||||||
}: IIntegrationParameterProps) => {
|
}: IIntegrationParameterProps) => {
|
||||||
const value = config.parameters[definition.name] || '';
|
const value = config.parameters[definition?.name] || '';
|
||||||
const type = resolveType(definition, value);
|
const type = resolveType(
|
||||||
|
definition,
|
||||||
|
typeof value === 'string' ? value : ''
|
||||||
|
);
|
||||||
const error = parametersErrors[definition.name];
|
const error = parametersErrors[definition.name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IAddonProvider } from 'interfaces/addons';
|
|
||||||
import {
|
import {
|
||||||
IntegrationParameter,
|
IntegrationParameter,
|
||||||
IIntegrationParameterProps,
|
IIntegrationParameterProps,
|
||||||
} from './IntegrationParameter/IntegrationParameter';
|
} from './IntegrationParameter/IntegrationParameter';
|
||||||
import { StyledTitle } from '../IntegrationForm.styles';
|
import { StyledTitle } from '../IntegrationForm.styles';
|
||||||
|
import type { AddonTypeSchema } from 'openapi';
|
||||||
|
|
||||||
interface IIntegrationParametersProps {
|
interface IIntegrationParametersProps {
|
||||||
provider?: IAddonProvider;
|
provider?: AddonTypeSchema;
|
||||||
parametersErrors: IIntegrationParameterProps['parametersErrors'];
|
parametersErrors: IIntegrationParameterProps['parametersErrors'];
|
||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
setParameterValue: IIntegrationParameterProps['setParameterValue'];
|
setParameterValue: IIntegrationParameterProps['setParameterValue'];
|
||||||
@ -32,7 +32,7 @@ export const IntegrationParameters = ({
|
|||||||
when saving.
|
when saving.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{provider.parameters.map(parameter => (
|
{provider.parameters?.map(parameter => (
|
||||||
<IntegrationParameter
|
<IntegrationParameter
|
||||||
key={parameter.name}
|
key={parameter.name}
|
||||||
definition={parameter}
|
definition={parameter}
|
||||||
|
@ -3,16 +3,16 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||||
import { IAddonProvider } from 'interfaces/addons';
|
import { AddonTypeSchema } from 'openapi';
|
||||||
|
|
||||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
marginLeft: theme.spacing(1),
|
marginLeft: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface IIntegrationNameCellProps {
|
interface IAddonNameCellProps {
|
||||||
provider: Pick<
|
provider: Pick<
|
||||||
IAddonProvider,
|
AddonTypeSchema,
|
||||||
'displayName' | 'description' | 'deprecated'
|
'displayName' | 'description' | 'deprecated'
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
@ -20,9 +20,7 @@ interface IIntegrationNameCellProps {
|
|||||||
/**
|
/**
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
* @deprecated Remove when integrationsRework flag is removed
|
||||||
*/
|
*/
|
||||||
export const IntegrationNameCell = ({
|
export const AddonNameCell = ({ provider }: IAddonNameCellProps) => (
|
||||||
provider,
|
|
||||||
}: IIntegrationNameCellProps) => (
|
|
||||||
<HighlightCell
|
<HighlightCell
|
||||||
value={provider.displayName}
|
value={provider.displayName}
|
||||||
subtitle={provider.description}
|
subtitle={provider.description}
|
@ -0,0 +1,21 @@
|
|||||||
|
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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -18,22 +18,11 @@ 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 { ConfigureAddonsButton } from './ConfigureAddonButton/ConfigureAddonsButton';
|
import { ConfigureAddonsButton } from './ConfigureAddonButton/ConfigureAddonsButton';
|
||||||
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
||||||
import { IntegrationNameCell } from '../IntegrationNameCell/IntegrationNameCell';
|
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
|
||||||
import { IAddonInstallation } from 'interfaces/addons';
|
import type { AddonTypeSchema } from 'openapi';
|
||||||
|
|
||||||
interface IProvider {
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
description: string;
|
|
||||||
documentationUrl: string;
|
|
||||||
parameters: object[];
|
|
||||||
events: string[];
|
|
||||||
installation?: IAddonInstallation;
|
|
||||||
deprecated?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAvailableAddonsProps {
|
interface IAvailableAddonsProps {
|
||||||
providers: IProvider[];
|
providers: AddonTypeSchema[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +73,7 @@ export const AvailableAddons = ({
|
|||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
width: '90%',
|
width: '90%',
|
||||||
Cell: ({ row: { original } }: any) => (
|
Cell: ({ row: { original } }: any) => (
|
||||||
<IntegrationNameCell provider={original} />
|
<AddonNameCell provider={original} />
|
||||||
),
|
),
|
||||||
sortType: 'alphanumeric',
|
sortType: 'alphanumeric',
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
|
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
|
||||||
import { IAddonProvider } from 'interfaces/addons';
|
import type { AddonTypeSchema } from 'openapi';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface IConfigureAddonsButtonProps {
|
interface IConfigureAddonsButtonProps {
|
||||||
provider: IAddonProvider;
|
provider: AddonTypeSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,36 @@
|
|||||||
|
import { type VFC } from 'react';
|
||||||
|
import type { AddonTypeSchema } from 'openapi';
|
||||||
|
import useLoading from 'hooks/useLoading';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { VFC } from 'react';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||||
|
import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||||
|
|
||||||
interface IAvailableIntegrationsProps {}
|
interface IAvailableIntegrationsProps {
|
||||||
|
providers: AddonTypeSchema[];
|
||||||
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = () => {
|
loading?: boolean;
|
||||||
return <PageContent>Available integrations</PageContent>;
|
}
|
||||||
|
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||||
|
providers,
|
||||||
|
loading,
|
||||||
|
}) => {
|
||||||
|
const ref = useLoading(loading || false);
|
||||||
|
return (
|
||||||
|
<PageContent
|
||||||
|
header={<PageHeader title="Available integrations" />}
|
||||||
|
isLoading={loading}
|
||||||
|
>
|
||||||
|
<StyledCardsGrid ref={ref}>
|
||||||
|
{providers?.map(({ name, displayName, description }) => (
|
||||||
|
<IntegrationCard
|
||||||
|
key={name}
|
||||||
|
icon={name}
|
||||||
|
title={displayName || name}
|
||||||
|
description={description}
|
||||||
|
link={`/integrations/create/${name}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledCardsGrid>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,6 @@ import { PageContent } from 'component/common/PageContent/PageContent';
|
|||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||||
import { IAddon } from 'interfaces/addons';
|
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
@ -15,7 +14,8 @@ 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 { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
||||||
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
|
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
|
||||||
import { IntegrationNameCell } from '../IntegrationNameCell/IntegrationNameCell';
|
import { AddonNameCell } from '../AddonNameCell/AddonNameCell';
|
||||||
|
import { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Remove when integrationsRework flag is removed
|
* @deprecated Remove when integrationsRework flag is removed
|
||||||
@ -25,7 +25,7 @@ export const ConfiguredAddons = () => {
|
|||||||
const { updateAddon, removeAddon } = useAddonsApi();
|
const { updateAddon, removeAddon } = useAddonsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const [deletedAddon, setDeletedAddon] = useState<IAddon>({
|
const [deletedAddon, setDeletedAddon] = useState<AddonSchema>({
|
||||||
id: 0,
|
id: 0,
|
||||||
provider: '',
|
provider: '',
|
||||||
description: '',
|
description: '',
|
||||||
@ -48,7 +48,7 @@ export const ConfiguredAddons = () => {
|
|||||||
}, [addons, loading]);
|
}, [addons, loading]);
|
||||||
|
|
||||||
const toggleAddon = useCallback(
|
const toggleAddon = useCallback(
|
||||||
async (addon: IAddon) => {
|
async (addon: AddonSchema) => {
|
||||||
try {
|
try {
|
||||||
await updateAddon({ ...addon, enabled: !addon.enabled });
|
await updateAddon({ ...addon, enabled: !addon.enabled });
|
||||||
refetchAddons();
|
refetchAddons();
|
||||||
@ -91,7 +91,7 @@ export const ConfiguredAddons = () => {
|
|||||||
original: { provider, description },
|
original: { provider, description },
|
||||||
},
|
},
|
||||||
}: any) => (
|
}: any) => (
|
||||||
<IntegrationNameCell
|
<AddonNameCell
|
||||||
provider={{
|
provider={{
|
||||||
...(providers.find(
|
...(providers.find(
|
||||||
({ name }) => name === provider
|
({ name }) => name === provider
|
||||||
@ -111,7 +111,7 @@ export const ConfiguredAddons = () => {
|
|||||||
Cell: ({
|
Cell: ({
|
||||||
row: { original },
|
row: { original },
|
||||||
}: {
|
}: {
|
||||||
row: { original: IAddon };
|
row: { original: AddonSchema };
|
||||||
}) => (
|
}) => (
|
||||||
<ConfiguredAddonsActionsCell
|
<ConfiguredAddonsActionsCell
|
||||||
key={original.id}
|
key={original.id}
|
||||||
@ -163,7 +163,7 @@ export const ConfiguredAddons = () => {
|
|||||||
useSortBy
|
useSortBy
|
||||||
);
|
);
|
||||||
|
|
||||||
const onRemoveAddon = async (addon: IAddon) => {
|
const onRemoveAddon = async (addon: AddonSchema) => {
|
||||||
try {
|
try {
|
||||||
await removeAddon(addon.id);
|
await removeAddon(addon.id);
|
||||||
refetchAddons();
|
refetchAddons();
|
||||||
|
@ -8,14 +8,14 @@ import {
|
|||||||
UPDATE_ADDON,
|
UPDATE_ADDON,
|
||||||
DELETE_ADDON,
|
DELETE_ADDON,
|
||||||
} from 'component/providers/AccessProvider/permissions';
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
import { IAddon } from 'interfaces/addons';
|
import { AddonSchema } from 'openapi';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface IConfiguredAddonsActionsCellProps {
|
interface IConfiguredAddonsActionsCellProps {
|
||||||
toggleAddon: (addon: IAddon) => Promise<void>;
|
toggleAddon: (addon: AddonSchema) => Promise<void>;
|
||||||
original: IAddon;
|
original: AddonSchema;
|
||||||
setShowDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setDeletedAddon: React.Dispatch<React.SetStateAction<IAddon>>;
|
setDeletedAddon: React.Dispatch<React.SetStateAction<AddonSchema>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import { AddonSchema, AddonTypeSchema } from 'openapi';
|
||||||
|
import useLoading from 'hooks/useLoading';
|
||||||
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||||
|
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||||
|
import { VFC } from 'react';
|
||||||
|
|
||||||
|
type ConfiguredIntegrationsProps = {
|
||||||
|
loading: boolean;
|
||||||
|
addons: AddonSchema[];
|
||||||
|
providers: AddonTypeSchema[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({
|
||||||
|
loading,
|
||||||
|
addons,
|
||||||
|
providers,
|
||||||
|
}) => {
|
||||||
|
const counter = addons.length ? `(${addons.length})` : '';
|
||||||
|
const ref = useLoading(loading || false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent
|
||||||
|
header={<PageHeader title={`Configured integrations ${counter}`} />}
|
||||||
|
sx={theme => ({ marginBottom: theme.spacing(2) })}
|
||||||
|
isLoading={loading}
|
||||||
|
>
|
||||||
|
<StyledCardsGrid ref={ref}>
|
||||||
|
{addons
|
||||||
|
?.sort(({ id: a }, { id: b }) => a - b)
|
||||||
|
.map(addon => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
enabled,
|
||||||
|
provider,
|
||||||
|
description,
|
||||||
|
// events,
|
||||||
|
// projects,
|
||||||
|
} = addon;
|
||||||
|
const providerConfig = providers.find(
|
||||||
|
item => item.name === provider
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntegrationCard
|
||||||
|
key={`${id}-${provider}-${enabled}`}
|
||||||
|
addon={addon}
|
||||||
|
icon={provider}
|
||||||
|
title={providerConfig?.displayName || provider}
|
||||||
|
isEnabled={enabled}
|
||||||
|
description={description || ''}
|
||||||
|
isConfigured
|
||||||
|
link={`/integrations/edit/${id}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StyledCardsGrid>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,101 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
import { IntegrationCardMenu } from './IntegrationCardMenu/IntegrationCardMenu';
|
||||||
|
import type { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
|
interface IIntegrationCardProps {
|
||||||
|
id?: string | number;
|
||||||
|
icon?: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
isConfigured?: boolean;
|
||||||
|
isEnabled?: boolean;
|
||||||
|
configureActionText?: string;
|
||||||
|
link: string;
|
||||||
|
addon?: AddonSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
boxShadow: theme.boxShadows.card,
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled(Typography)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAction = styled(Typography)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
|
marginTop: 'auto',
|
||||||
|
paddingTop: theme.spacing(1),
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const IntegrationCard: VFC<IIntegrationCardProps> = ({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
isEnabled,
|
||||||
|
configureActionText = 'Configure',
|
||||||
|
link,
|
||||||
|
addon,
|
||||||
|
}) => {
|
||||||
|
const isConfigured = addon !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledLink to={link}>
|
||||||
|
<StyledHeader>
|
||||||
|
<StyledTitle variant="h3" data-loading>
|
||||||
|
<IntegrationIcon name={icon as string} /> {title}
|
||||||
|
</StyledTitle>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isEnabled === true}
|
||||||
|
show={
|
||||||
|
<Badge color="success" data-loading>
|
||||||
|
Enabled
|
||||||
|
</Badge>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isEnabled === false}
|
||||||
|
show={<Badge data-loading>Disabled</Badge>}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isConfigured}
|
||||||
|
show={<IntegrationCardMenu addon={addon as AddonSchema} />}
|
||||||
|
/>
|
||||||
|
</StyledHeader>
|
||||||
|
<Typography variant="body1" data-loading>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
<StyledAction data-loading>
|
||||||
|
{configureActionText} <ChevronRightIcon />
|
||||||
|
</StyledAction>
|
||||||
|
</StyledLink>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,150 @@
|
|||||||
|
import { useCallback, useState, VFC } from 'react';
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
styled,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mui/material';
|
||||||
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
|
||||||
|
import { Delete, PowerSettingsNew } from '@mui/icons-material';
|
||||||
|
import {
|
||||||
|
DELETE_ADDON,
|
||||||
|
UPDATE_ADDON,
|
||||||
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { useHasRootAccess } from 'hooks/useHasAccess';
|
||||||
|
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||||
|
import type { AddonSchema } from 'openapi';
|
||||||
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
|
||||||
|
interface IIntegrationCardMenuProps {
|
||||||
|
addon: AddonSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledMenu = styled('div')(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
marginTop: theme.spacing(-1),
|
||||||
|
marginBottom: theme.spacing(-1),
|
||||||
|
marginRight: theme.spacing(-1),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const IntegrationCardMenu: VFC<IIntegrationCardMenuProps> = ({
|
||||||
|
addon,
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||||
|
const { updateAddon, removeAddon } = useAddonsApi();
|
||||||
|
const { refetchAddons } = useAddons();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const handleMenuClick = (event: React.SyntheticEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (open) {
|
||||||
|
setOpen(false);
|
||||||
|
setAnchorEl(null);
|
||||||
|
} else {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const updateAccess = useHasRootAccess(UPDATE_ADDON);
|
||||||
|
const deleteAccess = useHasRootAccess(DELETE_ADDON);
|
||||||
|
|
||||||
|
const toggleIntegration = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await updateAddon({ ...addon, enabled: !addon.enabled });
|
||||||
|
refetchAddons();
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: !addon.enabled
|
||||||
|
? 'Integration is now enabled'
|
||||||
|
: 'Integration is now disabled',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
}, [setToastApiError, refetchAddons, setToastData, updateAddon]);
|
||||||
|
|
||||||
|
const deleteIntegration = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await removeAddon(addon.id);
|
||||||
|
refetchAddons();
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
text: 'Integration has been deleted',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
}, [setToastApiError, refetchAddons, setToastData, removeAddon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMenu>
|
||||||
|
<Tooltip title="More actions" arrow>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleMenuClick}
|
||||||
|
size="small"
|
||||||
|
aria-controls={open ? 'actions-menu' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={open ? 'true' : undefined}
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
<MoreVertIcon sx={{ width: 32, height: 32 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu
|
||||||
|
id="project-card-menu"
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
onClick={event => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
onClose={handleMenuClick}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleIntegration();
|
||||||
|
}}
|
||||||
|
disabled={!updateAccess}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PowerSettingsNew />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{addon.enabled ? 'Disable' : 'Enable'}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>{' '}
|
||||||
|
<MenuItem
|
||||||
|
disabled={!deleteAccess}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteIntegration();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Delete />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Delete</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</StyledMenu>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,8 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
export const StyledCardsGrid = styled('div')(({ theme }) => ({
|
||||||
|
gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
|
||||||
|
gridAutoRows: '1fr',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
display: 'grid',
|
||||||
|
}));
|
@ -1,28 +1,38 @@
|
|||||||
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
|
import { VFC } from 'react';
|
||||||
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
||||||
|
import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations';
|
||||||
|
import { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
export const IntegrationList = () => {
|
export const IntegrationList: VFC = () => {
|
||||||
const { providers, addons, loading } = useAddons();
|
const { providers, addons, loading } = useAddons();
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const integrationsRework = uiConfig?.flags?.integrationsRework || false;
|
const loadingPlaceholderAddons: AddonSchema[] = Array.from({ length: 4 })
|
||||||
|
.fill({})
|
||||||
|
.map((_, id) => ({
|
||||||
|
id,
|
||||||
|
provider: 'mock',
|
||||||
|
description: 'mock integratino',
|
||||||
|
events: [],
|
||||||
|
projects: [],
|
||||||
|
parameters: {},
|
||||||
|
enabled: false,
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={addons.length > 0}
|
condition={addons.length > 0}
|
||||||
show={<ConfiguredAddons />}
|
show={
|
||||||
|
<ConfiguredIntegrations
|
||||||
|
addons={loading ? loadingPlaceholderAddons : addons}
|
||||||
|
providers={providers}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
|
||||||
condition={integrationsRework}
|
|
||||||
show={<AvailableIntegrations />}
|
|
||||||
elseShow={
|
|
||||||
<AvailableAddons loading={loading} providers={providers} />
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<AvailableIntegrations providers={providers} loading={loading} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,7 @@ 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 { TemporaryApplicationListWrapper } from 'component/application/ApplicationList/TemporaryApplicationListWrapper';
|
import { TemporaryApplicationListWrapper } from 'component/application/ApplicationList/TemporaryApplicationListWrapper';
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
@ -321,7 +322,7 @@ export const routes: IRoute[] = [
|
|||||||
{
|
{
|
||||||
path: '/addons',
|
path: '/addons',
|
||||||
title: 'Addons',
|
title: 'Addons',
|
||||||
component: IntegrationList,
|
component: AddonsList,
|
||||||
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
|
// TODO: use AddonRedirect after removing `integrationsRework` menu flag
|
||||||
hidden: false,
|
hidden: false,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IAddon } from 'interfaces/addons';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
|
import type { AddonSchema } from 'openapi';
|
||||||
|
|
||||||
const useAddonsApi = () => {
|
const useAddonsApi = () => {
|
||||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
@ -9,7 +9,7 @@ const useAddonsApi = () => {
|
|||||||
|
|
||||||
const URI = 'api/admin/addons';
|
const URI = 'api/admin/addons';
|
||||||
|
|
||||||
const createAddon = async (addonConfig: IAddon) => {
|
const createAddon = async (addonConfig: Omit<AddonSchema, 'id'>) => {
|
||||||
const path = URI;
|
const path = URI;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -29,7 +29,7 @@ const useAddonsApi = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateAddon = useCallback(
|
const updateAddon = useCallback(
|
||||||
async (addonConfig: IAddon) => {
|
async (addonConfig: AddonSchema) => {
|
||||||
const path = `${URI}/${addonConfig.id}`;
|
const path = `${URI}/${addonConfig.id}`;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
@ -2,12 +2,7 @@ import useSWR, { mutate, SWRConfiguration } from 'swr';
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
import { IAddon, IAddonProvider } from 'interfaces/addons';
|
import { AddonsSchema } from 'openapi';
|
||||||
|
|
||||||
interface IAddonsResponse {
|
|
||||||
addons: IAddon[];
|
|
||||||
providers: IAddonProvider[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const useAddons = (options: SWRConfiguration = {}) => {
|
const useAddons = (options: SWRConfiguration = {}) => {
|
||||||
const fetcher = async () => {
|
const fetcher = async () => {
|
||||||
@ -20,7 +15,7 @@ const useAddons = (options: SWRConfiguration = {}) => {
|
|||||||
|
|
||||||
const KEY = `api/admin/addons`;
|
const KEY = `api/admin/addons`;
|
||||||
|
|
||||||
const { data, error } = useSWR<IAddonsResponse>(KEY, fetcher, options);
|
const { data, error } = useSWR<AddonsSchema>(KEY, fetcher, options);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
const [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetchAddons = useCallback(() => {
|
const refetchAddons = useCallback(() => {
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import { ITagType } from './tags';
|
|
||||||
|
|
||||||
export interface IAddon {
|
|
||||||
provider: string;
|
|
||||||
parameters: Record<string, any>;
|
|
||||||
id: number;
|
|
||||||
events: string[];
|
|
||||||
projects?: string[];
|
|
||||||
environments?: string[];
|
|
||||||
enabled: boolean;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddonProvider {
|
|
||||||
description: string;
|
|
||||||
displayName: string;
|
|
||||||
documentationUrl: string;
|
|
||||||
events: string[];
|
|
||||||
name: string;
|
|
||||||
parameters: IAddonProviderParams[];
|
|
||||||
tagTypes: ITagType[];
|
|
||||||
installation?: IAddonInstallation;
|
|
||||||
alerts?: IAddonAlert[];
|
|
||||||
deprecated?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddonInstallation {
|
|
||||||
url: string;
|
|
||||||
warning?: string;
|
|
||||||
title?: string;
|
|
||||||
helpText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddonAlert {
|
|
||||||
type: 'success' | 'info' | 'warning' | 'error';
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddonProviderParams {
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
type: string;
|
|
||||||
required: boolean;
|
|
||||||
sensitive: boolean;
|
|
||||||
placeholder?: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddonConfig {
|
|
||||||
provider: string;
|
|
||||||
parameters: Record<string, any>;
|
|
||||||
id: number;
|
|
||||||
events: string[];
|
|
||||||
projects?: string[];
|
|
||||||
environments?: string[];
|
|
||||||
enabled: boolean;
|
|
||||||
description: string;
|
|
||||||
}
|
|
@ -39,6 +39,7 @@ process.nextTick(async () => {
|
|||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
slackAppAddon: true,
|
slackAppAddon: true,
|
||||||
lastSeenByEnvironment: true,
|
lastSeenByEnvironment: true,
|
||||||
|
integrationsRework: true,
|
||||||
newApplicationList: true,
|
newApplicationList: true,
|
||||||
featureNamingPattern: true,
|
featureNamingPattern: true,
|
||||||
doraMetrics: true,
|
doraMetrics: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user