mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: jira plugin page (#4627)
![image](https://github.com/Unleash/unleash/assets/964450/b58d3d27-4939-42b0-bf0d-51a31aa93dde) ![image](https://github.com/Unleash/unleash/assets/964450/acceedc4-9d53-461a-910f-b60dae0d6b6a)
This commit is contained in:
parent
3b754ec7ed
commit
1d414db982
1
frontend/src/assets/icons/jira-comment.svg
Normal file
1
frontend/src/assets/icons/jira-comment.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="2500" preserveAspectRatio="xMidYMid" width="2500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 -30.632388516510233 255.324 285.95638851651023"><linearGradient id="a"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient><linearGradient id="b" x1="98.031%" x2="58.888%" xlink:href="#a" y1=".161%" y2="40.766%"/><linearGradient id="c" x1="100.665%" x2="55.402%" xlink:href="#a" y1=".455%" y2="44.727%"/><path d="M244.658 0H121.707a55.502 55.502 0 0 0 55.502 55.502h22.649V77.37c.02 30.625 24.841 55.447 55.466 55.467V10.666C255.324 4.777 250.55 0 244.658 0z" fill="#2684ff"/><path d="M183.822 61.262H60.872c.019 30.625 24.84 55.447 55.466 55.467h22.649v21.938c.039 30.625 24.877 55.43 55.502 55.43V71.93c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#b)"/><path d="M122.951 122.489H0c0 30.653 24.85 55.502 55.502 55.502h22.72v21.867c.02 30.597 24.798 55.408 55.396 55.466V133.156c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#c)"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -1 +1,15 @@
|
||||
<svg height="2500" preserveAspectRatio="xMidYMid" width="2500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 -30.632388516510233 255.324 285.95638851651023"><linearGradient id="a"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient><linearGradient id="b" x1="98.031%" x2="58.888%" xlink:href="#a" y1=".161%" y2="40.766%"/><linearGradient id="c" x1="100.665%" x2="55.402%" xlink:href="#a" y1=".455%" y2="44.727%"/><path d="M244.658 0H121.707a55.502 55.502 0 0 0 55.502 55.502h22.649V77.37c.02 30.625 24.841 55.447 55.466 55.467V10.666C255.324 4.777 250.55 0 244.658 0z" fill="#2684ff"/><path d="M183.822 61.262H60.872c.019 30.625 24.84 55.447 55.466 55.467h22.649v21.938c.039 30.625 24.877 55.43 55.502 55.43V71.93c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#b)"/><path d="M122.951 122.489H0c0 30.653 24.85 55.502 55.502 55.502h22.72v21.867c.02 30.597 24.798 55.408 55.396 55.466V133.156c0-5.891-4.776-10.667-10.667-10.667z" fill="url(#c)"/></svg>
|
||||
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.2987 15.1469L17.9562 1.58157L16.6466 0.25L6.61925 10.4448L2.03534 15.1469C1.54421 15.6464 1.54421 16.4369 2.03534 16.8947L11.2032 26.2157L16.6466 31.75L26.6738 21.5552L26.8375 21.3887L31.2987 16.8947C31.7898 16.3952 31.7898 15.6048 31.2987 15.1469ZM16.6466 20.6813L12.0626 16.0208L16.6466 11.3603L21.2305 16.0208L16.6466 20.6813Z" fill="#2684FF"/>
|
||||
<path d="M16.6465 11.3451C13.6587 8.31147 13.6178 3.36621 16.6055 0.291016L6.61914 10.4724L12.0625 15.9995L16.6465 11.3451Z" fill="url(#paint0_linear_17148_2032)"/>
|
||||
<path d="M21.2714 15.959L16.6465 20.6549C19.6752 23.73 19.6752 28.6752 16.6465 31.7505L26.7148 21.5276L21.2714 15.959Z" fill="url(#paint1_linear_17148_2032)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17148_2032" x1="15.8439" y1="6.62639" x2="9.34422" y2="13.0277" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.176" stop-color="#0052CC"/>
|
||||
<stop offset="1" stop-color="#2684FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17148_2032" x1="17.5385" y1="25.3007" x2="24.0253" y2="18.9121" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.176" stop-color="#0052CC"/>
|
||||
<stop offset="1" stop-color="#2684FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/assets/img/jira/connect.png
Normal file
BIN
frontend/src/assets/img/jira/connect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 243 KiB |
BIN
frontend/src/assets/img/jira/cr.png
Normal file
BIN
frontend/src/assets/img/jira/cr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 243 KiB |
BIN
frontend/src/assets/img/jira/manage.png
Normal file
BIN
frontend/src/assets/img/jira/manage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 243 KiB |
@ -26,7 +26,7 @@ interface ICreateProps {
|
||||
loading?: boolean;
|
||||
modal?: boolean;
|
||||
disablePadding?: boolean;
|
||||
formatApiCode: () => string;
|
||||
formatApiCode?: () => string;
|
||||
}
|
||||
|
||||
const StyledContainer = styled('section', {
|
||||
@ -165,22 +165,43 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
||||
const { setToastData } = useToast();
|
||||
const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
|
||||
const copyCommand = () => {
|
||||
if (copy(formatApiCode())) {
|
||||
setToastData({
|
||||
title: 'Successfully copied the command',
|
||||
text: 'The command should now be automatically copied to your clipboard',
|
||||
autoHideDuration: 6000,
|
||||
type: 'success',
|
||||
show: true,
|
||||
});
|
||||
} else {
|
||||
setToastData({
|
||||
title: 'Could not copy the command',
|
||||
text: 'Sorry, but we could not copy the command.',
|
||||
autoHideDuration: 6000,
|
||||
type: 'error',
|
||||
show: true,
|
||||
});
|
||||
if (formatApiCode !== undefined) {
|
||||
if (copy(formatApiCode())) {
|
||||
setToastData({
|
||||
title: 'Successfully copied the command',
|
||||
text: 'The command should now be automatically copied to your clipboard',
|
||||
autoHideDuration: 6000,
|
||||
type: 'success',
|
||||
show: true,
|
||||
});
|
||||
} else {
|
||||
setToastData({
|
||||
title: 'Could not copy the command',
|
||||
text: 'Sorry, but we could not copy the command.',
|
||||
autoHideDuration: 6000,
|
||||
type: 'error',
|
||||
show: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderApiInfo = (apiDisabled: boolean) => {
|
||||
if (!apiDisabled) {
|
||||
return (
|
||||
<>
|
||||
<StyledSidebarDivider />
|
||||
<StyledSubtitle>
|
||||
API Command{' '}
|
||||
<Tooltip title="Copy command" arrow>
|
||||
<IconButton onClick={copyCommand} size="large">
|
||||
<StyledIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</StyledSubtitle>
|
||||
<Codebox text={formatApiCode!()} />{' '}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -221,16 +242,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
||||
documentationLink={documentationLink}
|
||||
documentationLinkLabel={documentationLinkLabel}
|
||||
>
|
||||
<StyledSidebarDivider />
|
||||
<StyledSubtitle>
|
||||
API Command{' '}
|
||||
<Tooltip title="Copy command" arrow>
|
||||
<IconButton onClick={copyCommand} size="large">
|
||||
<StyledIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</StyledSubtitle>
|
||||
<Codebox text={formatApiCode()} />
|
||||
{renderApiInfo(formatApiCode === undefined)}
|
||||
</Guidance>
|
||||
}
|
||||
/>
|
||||
|
@ -5,15 +5,19 @@ import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { IntegrationCard } from '../IntegrationCard/IntegrationCard';
|
||||
import { StyledCardsGrid } from '../IntegrationList.styles';
|
||||
import { JIRA_INFO } from '../../JiraIntegration/JiraIntegration';
|
||||
|
||||
interface IAvailableIntegrationsProps {
|
||||
providers: AddonTypeSchema[];
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||
providers,
|
||||
loading,
|
||||
}) => {
|
||||
const customProviders = [JIRA_INFO];
|
||||
|
||||
const ref = useLoading(loading || false);
|
||||
return (
|
||||
<PageContent
|
||||
@ -30,6 +34,16 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({
|
||||
link={`/integrations/create/${name}`}
|
||||
/>
|
||||
))}
|
||||
{customProviders?.map(({ name, displayName, description }) => (
|
||||
<IntegrationCard
|
||||
key={name}
|
||||
icon={name}
|
||||
title={displayName || name}
|
||||
description={description}
|
||||
link={`/integrations/view/${name}`}
|
||||
configureActionText={'View integration'}
|
||||
/>
|
||||
))}
|
||||
</StyledCardsGrid>
|
||||
</PageContent>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { DeviceHub } from '@mui/icons-material';
|
||||
import { formatAssetPath } from 'utils/formatPath';
|
||||
|
||||
import slackIcon from 'assets/icons/slack.svg';
|
||||
import jiraCommentIcon from 'assets/icons/jira-comment.svg';
|
||||
import jiraIcon from 'assets/icons/jira.svg';
|
||||
import webhooksIcon from 'assets/icons/webhooks.svg';
|
||||
import teamsIcon from 'assets/icons/teams.svg';
|
||||
@ -34,7 +35,7 @@ export const IntegrationIcon = ({ name }: IIntegrationIconProps) => {
|
||||
<img
|
||||
style={style}
|
||||
alt="JIRA logo"
|
||||
src={formatAssetPath(jiraIcon)}
|
||||
src={formatAssetPath(jiraCommentIcon)}
|
||||
/>
|
||||
);
|
||||
case 'webhook':
|
||||
@ -61,6 +62,14 @@ export const IntegrationIcon = ({ name }: IIntegrationIconProps) => {
|
||||
src={formatAssetPath(dataDogIcon)}
|
||||
/>
|
||||
);
|
||||
case 'jira':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="JIRA logo"
|
||||
src={formatAssetPath(jiraIcon)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Avatar>
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { styled, Typography } from '@mui/material';
|
||||
|
||||
import { formatAssetPath } from '../../../utils/formatPath';
|
||||
import { FC } from 'react';
|
||||
|
||||
export const StyledFigure = styled('figure')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(2),
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
export const StyledFigCaption = styled('figcaption')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(2),
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
export const StyledImg = styled('img')({
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
});
|
||||
|
||||
interface JiraIntegrationProps {
|
||||
title: string;
|
||||
description: string;
|
||||
src: string;
|
||||
}
|
||||
|
||||
export const JiraImageContainer: FC<JiraIntegrationProps> = ({
|
||||
title,
|
||||
description,
|
||||
src,
|
||||
}) => {
|
||||
return (
|
||||
<StyledFigure>
|
||||
<StyledFigCaption>
|
||||
<Typography variant={'h3'}>{title}</Typography>
|
||||
<Typography>{description}</Typography>
|
||||
</StyledFigCaption>
|
||||
<StyledImg src={formatAssetPath(src)} alt={title} />
|
||||
</StyledFigure>
|
||||
);
|
||||
};
|
@ -0,0 +1,114 @@
|
||||
import FormTemplate from '../../common/FormTemplate/FormTemplate';
|
||||
import { Divider, styled } from '@mui/material';
|
||||
|
||||
import { IntegrationIcon } from '../IntegrationList/IntegrationIcon/IntegrationIcon';
|
||||
import cr from 'assets/img/jira/cr.png';
|
||||
import connect from 'assets/img/jira/connect.png';
|
||||
import manage from 'assets/img/jira/manage.png';
|
||||
import React from 'react';
|
||||
import { JiraImageContainer } from './JiraImageContainer';
|
||||
|
||||
export const StyledContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const StyledGrayContainer = styled('div')(({ theme }) => ({
|
||||
backgroundColor: theme.palette.grey[100],
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
padding: theme.spacing(3),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StyledIconLine = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
marginLeft: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const StyledLink = styled('a')({
|
||||
textDecoration: 'none',
|
||||
});
|
||||
|
||||
export const JIRA_INFO = {
|
||||
name: 'jira',
|
||||
displayName: 'Jira',
|
||||
description:
|
||||
'Create, connect, manage, and approve Unleash feature flags directly from Jira',
|
||||
documentationUrl:
|
||||
'https://docs.getunleash.io/reference/integrations/jira-cloud-plugin-installation',
|
||||
};
|
||||
|
||||
export const JiraIntegration = () => {
|
||||
const { name, displayName, description, documentationUrl } = JIRA_INFO;
|
||||
|
||||
return (
|
||||
<FormTemplate
|
||||
title={`${displayName}`}
|
||||
description={description || ''}
|
||||
documentationLink={documentationUrl}
|
||||
documentationLinkLabel="Jira documentation"
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledGrayContainer>
|
||||
<StyledIconLine>
|
||||
<IntegrationIcon name={name} /> How does it work?
|
||||
</StyledIconLine>
|
||||
<ul>
|
||||
<li>
|
||||
Create a new feature flag directly within Jira, or
|
||||
connect existing flags to any Jira issue.
|
||||
</li>
|
||||
<li>
|
||||
Keep track of your flag status for each environment.
|
||||
</li>
|
||||
<li>
|
||||
Activate/deactivate feature flags directly within
|
||||
Jira.
|
||||
</li>
|
||||
<li>
|
||||
Initiate change requests when guarded flags are
|
||||
activated/deactivated within Jira.
|
||||
</li>
|
||||
</ul>
|
||||
</StyledGrayContainer>
|
||||
<StyledGrayContainer>
|
||||
<StyledLink
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://marketplace.atlassian.com/apps/1231447/unleash-enterprise-for-jira"
|
||||
>
|
||||
View plugin on Atlassian marketplace
|
||||
</StyledLink>
|
||||
</StyledGrayContainer>
|
||||
<Divider />
|
||||
<JiraImageContainer
|
||||
title={'Manage your feature flags for each environment'}
|
||||
description={
|
||||
'View your feature flag status for each of your environments. Quickly turn features on and off directly within Jira.'
|
||||
}
|
||||
src={manage}
|
||||
/>
|
||||
<Divider />
|
||||
<JiraImageContainer
|
||||
title={'Connect your feature flags to any Jira issue'}
|
||||
description={
|
||||
'Link as many feature flags as you want to any issue. Create new feature flags directly within Jira.'
|
||||
}
|
||||
src={connect}
|
||||
/>
|
||||
<Divider />
|
||||
<JiraImageContainer
|
||||
title={'Automatically initiate change requests'}
|
||||
description={
|
||||
'Automatically initiate change requests when you activate a guarded flag. You’ll receive a link inside Jira to review, approve, and apply the change.'
|
||||
}
|
||||
src={cr}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</FormTemplate>
|
||||
);
|
||||
};
|
@ -324,6 +324,14 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"title": "Create",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
"parent": "/integrations",
|
||||
"path": "/integrations/view/:providerId",
|
||||
"title": "View",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
|
@ -45,6 +45,7 @@ import { LoginHistory } from 'component/loginHistory/LoginHistory';
|
||||
import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList';
|
||||
import { AddonsList } from 'component/integrations/IntegrationList/AddonsList';
|
||||
import { TemporaryApplicationListWrapper } from 'component/application/ApplicationList/TemporaryApplicationListWrapper';
|
||||
import { JiraIntegration } from '../integrations/JiraIntegration/JiraIntegration';
|
||||
|
||||
export const routes: IRoute[] = [
|
||||
// Splash
|
||||
@ -337,6 +338,14 @@ export const routes: IRoute[] = [
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/integrations/view/:providerId',
|
||||
parent: '/integrations',
|
||||
title: 'View',
|
||||
component: JiraIntegration,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/integrations/edit/:addonId',
|
||||
parent: '/integrations',
|
||||
|
Loading…
Reference in New Issue
Block a user