diff --git a/frontend/src/assets/icons/jira-comment.svg b/frontend/src/assets/icons/jira-comment.svg new file mode 100644 index 0000000000..4ace5cc84a --- /dev/null +++ b/frontend/src/assets/icons/jira-comment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/jira.svg b/frontend/src/assets/icons/jira.svg index 4ace5cc84a..b29b8db9af 100644 --- a/frontend/src/assets/icons/jira.svg +++ b/frontend/src/assets/icons/jira.svg @@ -1 +1,15 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/img/jira/connect.png b/frontend/src/assets/img/jira/connect.png new file mode 100644 index 0000000000..894b332fc2 Binary files /dev/null and b/frontend/src/assets/img/jira/connect.png differ diff --git a/frontend/src/assets/img/jira/cr.png b/frontend/src/assets/img/jira/cr.png new file mode 100644 index 0000000000..894b332fc2 Binary files /dev/null and b/frontend/src/assets/img/jira/cr.png differ diff --git a/frontend/src/assets/img/jira/manage.png b/frontend/src/assets/img/jira/manage.png new file mode 100644 index 0000000000..894b332fc2 Binary files /dev/null and b/frontend/src/assets/img/jira/manage.png differ diff --git a/frontend/src/component/common/FormTemplate/FormTemplate.tsx b/frontend/src/component/common/FormTemplate/FormTemplate.tsx index 4f97820f95..fa7a84b130 100644 --- a/frontend/src/component/common/FormTemplate/FormTemplate.tsx +++ b/frontend/src/component/common/FormTemplate/FormTemplate.tsx @@ -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 = ({ 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 ( + <> + + + API Command{' '} + + + + + + + {' '} + + ); } }; @@ -221,16 +242,7 @@ const FormTemplate: React.FC = ({ documentationLink={documentationLink} documentationLinkLabel={documentationLinkLabel} > - - - API Command{' '} - - - - - - - + {renderApiInfo(formatApiCode === undefined)} } /> diff --git a/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx b/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx index fa75d7f0f0..2343d020b6 100644 --- a/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx +++ b/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx @@ -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 = ({ providers, loading, }) => { + const customProviders = [JIRA_INFO]; + const ref = useLoading(loading || false); return ( = ({ link={`/integrations/create/${name}`} /> ))} + {customProviders?.map(({ name, displayName, description }) => ( + + ))} ); diff --git a/frontend/src/component/integrations/IntegrationList/IntegrationIcon/IntegrationIcon.tsx b/frontend/src/component/integrations/IntegrationList/IntegrationIcon/IntegrationIcon.tsx index ee2e6f1e2c..b7c7fd822a 100644 --- a/frontend/src/component/integrations/IntegrationList/IntegrationIcon/IntegrationIcon.tsx +++ b/frontend/src/component/integrations/IntegrationList/IntegrationIcon/IntegrationIcon.tsx @@ -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) => { JIRA logo ); case 'webhook': @@ -61,6 +62,14 @@ export const IntegrationIcon = ({ name }: IIntegrationIconProps) => { src={formatAssetPath(dataDogIcon)} /> ); + case 'jira': + return ( + JIRA logo + ); default: return ( diff --git a/frontend/src/component/integrations/JiraIntegration/JiraImageContainer.tsx b/frontend/src/component/integrations/JiraIntegration/JiraImageContainer.tsx new file mode 100644 index 0000000000..cba28045c2 --- /dev/null +++ b/frontend/src/component/integrations/JiraIntegration/JiraImageContainer.tsx @@ -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 = ({ + title, + description, + src, +}) => { + return ( + + + {title} + {description} + + + + ); +}; diff --git a/frontend/src/component/integrations/JiraIntegration/JiraIntegration.tsx b/frontend/src/component/integrations/JiraIntegration/JiraIntegration.tsx new file mode 100644 index 0000000000..84c9c5d7d3 --- /dev/null +++ b/frontend/src/component/integrations/JiraIntegration/JiraIntegration.tsx @@ -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 ( + + + + + How does it work? + +
    +
  • + Create a new feature flag directly within Jira, or + connect existing flags to any Jira issue. +
  • +
  • + Keep track of your flag status for each environment. +
  • +
  • + Activate/deactivate feature flags directly within + Jira. +
  • +
  • + Initiate change requests when guarded flags are + activated/deactivated within Jira. +
  • +
+
+ + + View plugin on Atlassian marketplace + + + + + + + + +
+
+ ); +}; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index bfe0a24394..2c96ef3a01 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -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": {}, diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 6ee0a2daca..385b7180a4 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -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',