mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: jira plugin page (#4627)
 
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