diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.styles.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.styles.tsx index 1005b4de..91336367 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.styles.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.styles.tsx @@ -107,23 +107,22 @@ export const notConnectedSpinnerContainer = css` width: 100%; `; -// TODO - delete this temp class -export const notConnectedTempImgPlaceholder = css` - background-color: lightgrey; - border: 1px solid lightgrey; - border-radius: 3px; - height: 160px; - margin: auto; - width: 160px; +export const connectionPanelContainerContainer = css` + margin: ${token('space.600')} auto; + max-height: 400px; + min-height: 400px; + max-width: 420px; + position: relative; + text-align: center; `; -export const notConnectedStateHeader = css` +export const connectionPanelContainerHeader = css` font-size: 20px; font-weight: 500; margin: ${token('space.200')} auto; `; -export const notConnectedStateParagraph = css` +export const connectionPanelContainerParagraph = css` font-size: 14px; line-height: 20px; margin-bottom: ${token('space.400')}; @@ -233,13 +232,6 @@ export const setUpGuideOrderListItemHeader = css` margin-bottom: ${token('space.200')}; `; -export const setUpGuideLink = css` - background-color: inherit; - border: none; - color: ${token('color.link')}; - padding: ${token('space.0')} -`; - export const setUpGuideInfoPanel = css` background-color: #F7F8F9; display: flex; diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.test.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.test.tsx index 874e5dac..5a5e7269 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.test.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { + act, fireEvent, render, screen, @@ -105,6 +106,12 @@ const servers: JenkinsServer[] = [ lastUpdatedOn: new Date() }, pipelines: [] + }, + { + name: 'server eight', + uuid: '56046af9-d0eb-4efb-8896-iwer23rjesu', + pluginConfig: undefined, + pipelines: [] } ]; @@ -133,28 +140,28 @@ describe('Connection Panel Suite', () => { expect(result[0].connectedState).toEqual(ConnectedState.CONNECTED); }); - it('should correctly set state for two servers with different IPs', () => { - const twoServers: JenkinsServer[] = [servers[0], servers[1]]; - const result = addConnectedState(twoServers); - - expect(result[0].connectedState).toEqual(ConnectedState.CONNECTED); - expect(result[1].connectedState).toEqual(ConnectedState.PENDING); - }); - it('should correctly set state for multiple servers with duplicate IPs', () => { - const multipleServers: JenkinsServer[] = [servers[0], servers[2], servers[3]]; - const result = addConnectedState(multipleServers); + const multipleServers: JenkinsServer[] = [servers[0], servers[2], servers[3], servers[7]]; + const results = addConnectedState(multipleServers); - expect(result[0].connectedState).toEqual(ConnectedState.CONNECTED); - expect(result[1].connectedState).toEqual(ConnectedState.CONNECTED); - expect(result[2].connectedState).toEqual(ConnectedState.DUPLICATE); + expect(results[0].connectedState).toEqual(ConnectedState.CONNECTED); + expect(results[1].connectedState).toEqual(ConnectedState.CONNECTED); + expect(results[2].connectedState).toEqual(ConnectedState.DUPLICATE); + expect(results[3].connectedState).toEqual(ConnectedState.PENDING); }); - it('should handle servers with no pluginConfig', () => { - const noPluginConfig: JenkinsServer[] = [servers[4]]; - const result = addConnectedState(noPluginConfig); + it('should handle servers with missing data', () => { + const noPluginConfigAndNoPipelines: JenkinsServer[] = [servers[7]]; + const noPluginConfigButHasPipelines: JenkinsServer[] = [servers[4]]; + const hasPluginConfigButNoPipelines: JenkinsServer[] = [servers[6]]; + + const noPluginConfigAndNoPipelinesResult = addConnectedState(noPluginConfigAndNoPipelines); + const noPluginConfigButHasPipelinesResult = addConnectedState(noPluginConfigButHasPipelines); + const hasPluginConfigButNoPipelinesResult = addConnectedState(hasPluginConfigButNoPipelines); - expect(result[0].connectedState).toEqual(ConnectedState.PENDING); + expect(noPluginConfigButHasPipelinesResult[0].connectedState).toEqual(ConnectedState.CONNECTED); + expect(noPluginConfigAndNoPipelinesResult[0].connectedState).toEqual(ConnectedState.PENDING); + expect(hasPluginConfigButNoPipelinesResult[0].connectedState).toEqual(ConnectedState.CONNECTED); }); it('should correctly set state for multiple servers with duplicate IPs and no pipelines', () => { @@ -162,8 +169,9 @@ describe('Connection Panel Suite', () => { const result = addConnectedState(duplicateServers); expect(result[0].connectedState).toEqual(ConnectedState.CONNECTED); - expect(result[1].connectedState).toEqual(ConnectedState.PENDING); + expect(result[1].connectedState).toEqual(ConnectedState.CONNECTED); expect(result[2].connectedState).toEqual(ConnectedState.DUPLICATE); + expect(result[2].originalConnection).toEqual(servers[5].name); }); }); @@ -241,14 +249,7 @@ describe('Connection Panel Suite', () => { const server: JenkinsServer = { name: 'my server', connectedState: ConnectedState.PENDING, - pluginConfig: { - ipAddress: '10.0.0.1', - lastUpdatedOn: new Date(), - autoBuildRegex: '', - autoBuildEnabled: true, - autoDeploymentsEnabled: false, - autoDeploymentsRegex: '' - }, + pluginConfig: undefined, uuid: 'djsnfudin-jhsdwefwe-238hnfuwef', pipelines: [] }; @@ -261,11 +262,11 @@ describe('Connection Panel Suite', () => { ); const nameLabel = screen.getByText(server.name); - const ipAddressLabel = screen.getByText(`IP address: ${server.pluginConfig?.ipAddress}`); + const ipAddressLabel = screen.queryByText(`IP address: ${server.pluginConfig?.ipAddress}`); const statusLabel = screen.getByTestId('status-label'); expect(nameLabel).toBeInTheDocument(); - expect(ipAddressLabel).toBeInTheDocument(); + expect(ipAddressLabel).not.toBeInTheDocument(); expect(statusLabel).toHaveStyle({ color: '#a54900', backgroundColor: '#fff7d6' }); expect(statusLabel).toHaveTextContent('PENDING'); }); @@ -299,6 +300,7 @@ describe('Connection Panel Suite', () => { // TODO - add test for Rename - will be done when I build the new server name screen // TODO - add test for Connection settings - will be done when I build the new set up Jenkins screen + test('should handle server disconnection and refreshing correctly', async () => { jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce(servers); @@ -325,6 +327,53 @@ describe('Connection Panel Suite', () => { describe('Connection Panel Main', () => { const setJenkinsServers = jest.fn(); + test('should render panel content for PENDING server', async () => { + jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce([servers[7]]); + + render(); + + await waitFor(() => { + expect(screen.getByText('Connection pending')).toBeInTheDocument(); + }); + }); + + test('should render panel content for DUPLICATE server', async () => { + jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce([servers[5], servers[6]]); + + render(); + + await waitFor(() => { + expect(screen.getByText('Duplicate server')).toBeInTheDocument(); + }); + }); + + test('should render panel content for CONNECTED server without pipeline data', async () => { + jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce([servers[1]]); + + await act(async () => { + render(); + await waitFor(() => { + expect(screen.getByText('No data received')).toBeInTheDocument(); + expect(screen.queryByText('Pipeline')).not.toBeInTheDocument(); + expect(screen.queryByText('Event')).not.toBeInTheDocument(); + expect(screen.queryByText('Received')).not.toBeInTheDocument(); + }); + }); + }); + + test('should render panel content for CONNECTED server with pipeline data', async () => { + jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce([servers[5]]); + + render(); + + await waitFor(() => { + expect(screen.queryByText('No data received')).not.toBeInTheDocument(); + expect(screen.getByText('Pipeline')).toBeInTheDocument(); + expect(screen.getByText('Event')).toBeInTheDocument(); + expect(screen.getByText('Received')).toBeInTheDocument(); + }); + }); + test('should handle server deletion correctly for DUPLICATE SERVERS', async () => { jest.spyOn(getAllJenkinsServersModule, 'getAllJenkinsServers').mockResolvedValueOnce(servers); @@ -405,7 +454,6 @@ describe('Connection Panel Suite', () => { await waitFor(() => { expect(screen.getByText(server.name)).toBeInTheDocument(); - fireEvent.click(screen.getByText('Set up guide')); }); diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.tsx index 72111ae2..7838eb4e 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanel.tsx @@ -12,20 +12,32 @@ export const addConnectedState = (servers: JenkinsServer[]): JenkinsServer[] => return servers .slice() // Create a shallow copy to avoid mutating the original array .sort((a, b) => b.pipelines.length - a.pipelines.length) - .map((server: JenkinsServer) => { + .map((server: JenkinsServer, index, array) => { const ipAddress = server.pluginConfig?.ipAddress; let connectedState = ConnectedState.PENDING; + let originalConnection: string | undefined; if (ipAddress && ipAddressSet.has(ipAddress)) { connectedState = ConnectedState.DUPLICATE; - } else if (server.pipelines.length > 0 && ipAddress) { + + // Find the original connection with the same IP address + const originalServer = array.find( + (s) => s !== server && s.pluginConfig?.ipAddress === ipAddress && + s.connectedState !== ConnectedState.DUPLICATE + ); + + if (originalServer) { + originalConnection = originalServer.name; + } + } else if (server.pluginConfig || server.pipelines.length) { connectedState = ConnectedState.CONNECTED; - ipAddressSet.add(ipAddress); + if (ipAddress) ipAddressSet.add(ipAddress); } return { ...server, - connectedState + connectedState, + originalConnection }; }); }; diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelContent.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelContent.tsx new file mode 100644 index 00000000..db5300b2 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelContent.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { cx } from '@emotion/css'; +import Button, { Appearance, ButtonGroup } from '@atlaskit/button'; +import { ConnectedState } from '../StatusLabel/StatusLabel'; +import { + connectionPanelContainerContainer, + connectionPanelContainerHeader, + connectionPanelContainerParagraph +} from './ConnectionPanel.styles'; +import { ConnectionPendingIcon } from '../icons/ConnectionPendingIcon'; +import { NoDataIcon } from '../icons/NoDataIcon'; +import { DuplicateServerIcon } from '../icons/DuplicateServerIcon'; + +type NotConnectedStateProps = { + connectedState: ConnectedState; + contentHeader: string, + contentInstructionOne: string, + contentInstructionTwo?: string, + buttonAppearance: Appearance, + firstButtonLabel: string, + secondButtonLabel?: string, + buttonOneOnClick(data?: any): void, + buttonTwoOnClick?(): void, + testId?: string +}; + +const ConnectionPanelContent = ({ + connectedState, + contentHeader, + contentInstructionOne, + contentInstructionTwo, + buttonAppearance, + firstButtonLabel, + secondButtonLabel, + buttonOneOnClick, + buttonTwoOnClick, + testId +}: NotConnectedStateProps): JSX.Element => { + let icon; + + if (connectedState === ConnectedState.CONNECTED) { + icon = ; + } else if (connectedState === ConnectedState.PENDING) { + icon = ; + } else { + icon = ; + } + + return ( +
+ {icon} +

{contentHeader}

+

{contentInstructionOne}

+

{contentInstructionTwo}

+ + + { + secondButtonLabel + ? + : <> + } + +
+ ); +}; + +export { ConnectionPanelContent }; diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelMain.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelMain.tsx index e12550b2..62bf6000 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelMain.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/ConnectionPanelMain.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useState } from 'react'; import { cx } from '@emotion/css'; import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs'; import { @@ -13,6 +13,8 @@ import { NotConnectedState } from './NotConnectedState'; import { JenkinsServer } from '../../../../src/common/types'; import { ConnectedJenkinsServers } from './ConnectedJenkinsServers'; import { SetUpGuide, UpdateAvailable } from './SetUpGuide'; +import { InProductHelpDrawer } from '../InProductHelpDrawer/InProductHelpDrawer'; +import { ConnectionPanelContent } from './ConnectionPanelContent'; type PanelProps = { children: ReactNode, @@ -52,16 +54,37 @@ const ConnectionPanelMain = ({ jenkinsServer, refreshServers }: ConnectionPanelMainProps): JSX.Element => { + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const openDrawer = () => { + setIsDrawerOpen(true); + }; + + const handleClickSetupGuide = () => { + setSelectedTabIndex(1); + }; + + const handleTabSelect = (index: number) => { + setSelectedTabIndex(index); + }; + + const handleRefreshPanel = () => { + // TODO - ARC-2738 refresh functionality + }; + return (
+ { connectedState === ConnectedState.DUPLICATE ? - : + : { connectedState === ConnectedState.PENDING @@ -79,13 +102,28 @@ const ConnectionPanelMain = ({ { connectedState === ConnectedState.CONNECTED ? - + { + jenkinsServer.pipelines.length + ? + : + } : } @@ -94,11 +132,14 @@ const ConnectionPanelMain = ({ { jenkinsServer.pluginConfig ? - + :
- +
} diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.test.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.test.tsx index 415bf95b..56288a6e 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.test.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.test.tsx @@ -21,12 +21,14 @@ describe('NotConnectedState', () => { }; const refreshServers = jest.fn(); + const handleRefreshPanel = jest.fn(); test('renders with connected state DUPLICATE', () => { render(); expect(screen.getByText('Duplicate server')).toBeInTheDocument(); expect(screen.getByText('Delete')).toBeInTheDocument(); @@ -37,9 +39,11 @@ describe('NotConnectedState', () => { connectedState={ConnectedState.PENDING} jenkinsServer={mockServer} refreshServers={refreshServers} + handleRefreshPanel={handleRefreshPanel} />); expect(screen.getByText('Connection pending')).toBeInTheDocument(); - expect(screen.getByText('Connection settings')).toBeInTheDocument(); + expect(screen.getByText('Refresh')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); }); test('clicking delete button removes the server', async () => { @@ -48,6 +52,7 @@ describe('NotConnectedState', () => { connectedState={ConnectedState.DUPLICATE} jenkinsServer={mockServer} refreshServers={refreshServers} + handleRefreshPanel={handleRefreshPanel} />); fireEvent.click(screen.getByText('Delete')); diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.tsx index 4913e57c..f8f8011b 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/NotConnectedState.tsx @@ -1,29 +1,27 @@ import React, { useState } from 'react'; import { cx } from '@emotion/css'; -import Button from '@atlaskit/button'; -import { token } from '@atlaskit/tokens'; import Spinner from '@atlaskit/spinner'; import { ConnectedState } from '../StatusLabel/StatusLabel'; import { notConnectedSpinnerContainer, - notConnectedStateContainer, - notConnectedStateHeader, - notConnectedStateParagraph, - notConnectedTempImgPlaceholder + notConnectedStateContainer } from './ConnectionPanel.styles'; import { JenkinsServer } from '../../../../src/common/types'; import { disconnectJenkinsServer } from '../../api/disconnectJenkinsServer'; +import { ConnectionPanelContent } from './ConnectionPanelContent'; type NotConnectedStateProps = { connectedState: ConnectedState; jenkinsServer: JenkinsServer; refreshServers(serverToRemove: JenkinsServer): void; + handleRefreshPanel(serverToRemove: JenkinsServer): void; }; const NotConnectedState = ({ connectedState, + refreshServers, jenkinsServer, - refreshServers + handleRefreshPanel }: NotConnectedStateProps): JSX.Element => { const [isLoading, setIsLoading] = useState(false); @@ -34,7 +32,7 @@ const NotConnectedState = ({ await disconnectJenkinsServer(serverToDelete.uuid); } catch (e) { console.log('Failed to disconnect server', e); - // TODO - add error state ARC-2722 + // TODO - ARC-2722 handle error state } finally { setIsLoading(false); } @@ -42,23 +40,15 @@ const NotConnectedState = ({ refreshServers(serverToDelete); }; - const notConnectedHeader = - connectedState === ConnectedState.PENDING ? 'Connection pending' : 'Duplicate server'; - const notConnectedContent = - connectedState === ConnectedState.PENDING ? ( - <> - This connection is pending completion by a Jenkins admin. - Its set up guide will be available when the connection is complete. - - Open connection settings if your Jenkins admin needs to revisit the items they need. - - ) : ( - <> - This connection is a duplicate of SERVER NAME. - - Use SERVER NAME to manage this server. - - ); + const deleteServerWrapper = async () => { + await deleteServer(jenkinsServer); + }; + + const handleLearnMore = async () => { + // TODO - ARC-2736 IPH + }; + + const isPending = connectedState === ConnectedState.PENDING; return (
@@ -68,23 +58,29 @@ const NotConnectedState = ({
) : ( <> -
-

{notConnectedHeader}

-

{notConnectedContent}

- {/* TODO - add onClick handler for Connection settings - - will be done when I build the new set up Jenkins screen */} - {connectedState === ConnectedState.PENDING ? ( - - ) : ( - - )} + )}
diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.test.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.test.tsx index 3a635064..56c5a3aa 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.test.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { PipelineEventType, SetUpGuideInstructions, SetUpGuideLink } from './SetUpGuide'; +import { PipelineEventType, SetUpGuideInstructions } from './SetUpGuide'; describe('SetUpGuideInstructions', () => { const onClickMock = jest.fn(); @@ -60,15 +60,3 @@ describe('SetUpGuideInstructions', () => { expect(queryByText('OR')).toBeNull(); }); }); - -describe('SetUpGuideLink', () => { - const onClickMock = jest.fn(); - - test('renders SetUpGuideLink with label', () => { - const { getByText } = render( - - ); - - expect(getByText('build')).toBeInTheDocument(); - }); -}); diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.tsx index 18582c29..3e8025ef 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionPanel/SetUpGuide.tsx @@ -1,10 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import { cx } from '@emotion/css'; -import Drawer from '@atlaskit/drawer'; import PeopleGroup from '@atlaskit/icon/glyph/people-group'; import Button from '@atlaskit/button/standard-button'; import { - setUpGuideLink, setUpGuideInfoPanel, setUpGuideNestedOrderedList, setUpGuideNestedOrderedListItem, @@ -12,34 +10,20 @@ import { setUpGuideOrderedListItem, setUpGuideOrderListItemHeader, setUpGuideParagraph, - setUpGuideUpdateAvailableHeader, setUpGuideUpdateAvailableButtonContainer, setUpGuideUpdateAvailableContent, + setUpGuideUpdateAvailableHeader, setUpGuideUpdateAvailableIconContainer } from './ConnectionPanel.styles'; import { JenkinsPluginConfig } from '../../../../src/common/types'; import { UpdateAvailableIcon } from '../icons/UpdateAvailableIcon'; +import { InProductHelpAction, InProductHelpActionType } from '../InProductHelpDrawer/InProductHelpAction'; -type SetUpGuideLinkProps = { - onClick: (event: React.MouseEvent) => void, - label: string, +type UpdateAvailableProps = { + openDrawer(): void }; -export const SetUpGuideLink = ({ onClick, label }: SetUpGuideLinkProps): JSX.Element => { - return ( - - ); -}; - -type SetUpGuidePipelineStepInstructionProps = { - eventType: string, - onClick: (event: React.MouseEvent) => void, - pipelineStepLabel: string -}; - -export const UpdateAvailable = (): JSX.Element => { +export const UpdateAvailable = ({ openDrawer }: UpdateAvailableProps): JSX.Element => { return ( <> @@ -49,15 +33,20 @@ export const UpdateAvailable = (): JSX.Element => {

To access features like this set up guide, a Jenkins admin must log into this server and update the plugin.

- {/* TODO - implement link once the IPH mystery has been solved */} - - {/* TODO - uhh ummm when we figure out exactly how we intend this to work... :badpokerface: */} + + {/* TODO - ARC-2738 */}
); }; +type SetUpGuidePipelineStepInstructionProps = { + eventType: string, + onClick: (event: React.MouseEvent) => void, + pipelineStepLabel: string +}; + const SetUpGuidePipelineStepInstruction = ({ eventType, onClick, @@ -65,7 +54,12 @@ const SetUpGuidePipelineStepInstruction = ({ }: SetUpGuidePipelineStepInstructionProps): JSX.Element => { return (

Add a   -   +   step to the end of {eventType} stages.

); @@ -113,16 +107,21 @@ export const SetUpGuideInstructions = ({

Use   - '} + type={InProductHelpActionType.HelpLink} + appearance="link" />   in the names of the {eventType} stages.

); } else if (eventType === PipelineEventType.BUILD && globalSettings && !regex?.length) { - contentToRender =

; + contentToRender = +

+ +

; } else { contentToRender = ( { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - - const openDrawer = () => { - setIsDrawerOpen(true); - }; - - const onClose = () => { - setIsDrawerOpen(false); - }; - +const SetUpGuide = ({ + pluginConfig, + openDrawer +}: SetUpGuideProps): JSX.Element => { return ( <> - - {/* TODO - update this to render content for the 'link' clicked - (will be done after I dig into drawer usage */} -
Add content here for each link item
-

To receive build and deployment data from this server:

    @@ -176,7 +159,7 @@ const SetUpGuide = ({ pluginConfig }: SetUpGuideProps): JSX.Element => { Developers in your project teams

    Must enter their Jira issue keys - (e.g. ) + (e.g. ) into their branch names and commit message.

    @@ -203,7 +186,7 @@ const SetUpGuide = ({ pluginConfig }: SetUpGuideProps): JSX.Element => {

    Not sure who should use this guide? It depends how your teams use Jenkins.  - +

    diff --git a/app/jenkins-for-jira-ui/src/components/ConnectionWizard/ConnectionWizard.styles.tsx b/app/jenkins-for-jira-ui/src/components/ConnectionWizard/ConnectionWizard.styles.tsx index 5e3b339d..5fc30be0 100644 --- a/app/jenkins-for-jira-ui/src/components/ConnectionWizard/ConnectionWizard.styles.tsx +++ b/app/jenkins-for-jira-ui/src/components/ConnectionWizard/ConnectionWizard.styles.tsx @@ -1,6 +1,5 @@ import { css } from '@emotion/css'; import { token } from '@atlaskit/tokens'; -// import { token } from '@atlaskit/tokens'; export const connectionWizardContainer = css` align-items: center; diff --git a/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelp.styles.tsx b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelp.styles.tsx new file mode 100644 index 00000000..c13ac754 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelp.styles.tsx @@ -0,0 +1,19 @@ +import { css } from '@emotion/css'; +import { token } from '@atlaskit/tokens'; + +export const inProductHelpActionLink = css` + background-color: inherit; + border: none; + color: ${token('color.link')}; + margin-bottom: ${token('space.025')}; + padding: ${token('space.0')} !important; + + span { + font-weight: normal; + margin: ${token('space.0')}; + } + + &:hover { + text-decoration: none !important; + } +`; diff --git a/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpAction.tsx b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpAction.tsx new file mode 100644 index 00000000..d291d006 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpAction.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { cx } from '@emotion/css'; +import Button, { Appearance } from '@atlaskit/button'; +import { KeyboardOrMouseEvent } from '@atlaskit/modal-dialog'; +import { inProductHelpActionLink } from './InProductHelp.styles'; + +export enum InProductHelpActionType { + HelpLink = 'link', + HelpButton = 'button' +} + +type InProductHelpActionProps = { + onClick(e: KeyboardOrMouseEvent): void, + label: string, + type: InProductHelpActionType, + appearance: Appearance +}; + +export const InProductHelpAction = ({ + onClick, + label, + type, + appearance +}: InProductHelpActionProps): JSX.Element => { + const inProductHelpTypeClassName = + type === InProductHelpActionType.HelpLink ? inProductHelpActionLink : ''; + + return ( + + ); +}; diff --git a/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.test.tsx b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.test.tsx new file mode 100644 index 00000000..d772e6a2 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.test.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { InProductHelpAction, InProductHelpActionType } from './InProductHelpAction'; + +describe('InProductHelpAction', () => { + const onClickMock = jest.fn(); + + test('renders InProductHelpAction with label', () => { + const { getByText } = render( + + ); + + expect(getByText('build')).toBeInTheDocument(); + }); +}); diff --git a/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.tsx b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.tsx new file mode 100644 index 00000000..77f00a33 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/InProductHelpDrawer/InProductHelpDrawer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import Drawer from '@atlaskit/drawer'; + +type InProductHelpDrawerProps = { + isDrawerOpen: boolean, + setIsDrawerOpen(isDrawerOpen: boolean): void +}; + +export const InProductHelpDrawer = ({ isDrawerOpen, setIsDrawerOpen }: InProductHelpDrawerProps): JSX.Element => { + const closeDrawer = () => { + setIsDrawerOpen(false); + }; + + return ( + + {/* TODO - ARC-2737 Algolia implementation */} +
    Add content here for each link item
    +
    + ); +}; diff --git a/app/jenkins-for-jira-ui/src/components/JenkinsConfigurationForm/InProductHelpActionType.java b/app/jenkins-for-jira-ui/src/components/JenkinsConfigurationForm/InProductHelpActionType.java new file mode 100644 index 00000000..27ee7d71 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/JenkinsConfigurationForm/InProductHelpActionType.java @@ -0,0 +1,13 @@ +import React from 'react'; +import { cx } from '@emotion/css'; +import { setUpGuideLink } from '../ConnectionPanel/ConnectionPanel.styles'; + +export const InProductHelpDraw = (): JSX.Element => { + const inProductHelpTypeClassName = type === InProductHelpActionType.Link ? setUpGuideLink : ''; + + return ( + + ); +}; diff --git a/app/jenkins-for-jira-ui/src/components/ServerManagement/ServerManagement.tsx b/app/jenkins-for-jira-ui/src/components/ServerManagement/ServerManagement.tsx index 47f7fdd6..619fb6c5 100644 --- a/app/jenkins-for-jira-ui/src/components/ServerManagement/ServerManagement.tsx +++ b/app/jenkins-for-jira-ui/src/components/ServerManagement/ServerManagement.tsx @@ -51,11 +51,11 @@ const ServerManagement = (): JSX.Element => { const pageHeaderActions = ( - {/* TODO - add onClick event (will be done when I build the new server name form */} + {/* TODO handle empty state - ARC-2730 connection wizard */} - {/* TODO - add onClick event (will be done after spike for ARC-2691 */} + {/* TODO - ARC-2723 share modal */} ); diff --git a/app/jenkins-for-jira-ui/src/components/ServerManagement/TopPanel/TopPanel.tsx b/app/jenkins-for-jira-ui/src/components/ServerManagement/TopPanel/TopPanel.tsx index 9152400b..1ea2ad6a 100644 --- a/app/jenkins-for-jira-ui/src/components/ServerManagement/TopPanel/TopPanel.tsx +++ b/app/jenkins-for-jira-ui/src/components/ServerManagement/TopPanel/TopPanel.tsx @@ -15,12 +15,13 @@ const TopPanel = (): JSX.Element => {

    Server management

    - {/* TODO - add link/drawer (will be done after I investigate 'proper' drawer usage) */} -

    Jenkins for Jira lets your teams keep track of code - they build and deploy on Jenkins servers.

    - {/* TODO - add link/drawer (will be done after I investigate 'proper' drawer usage) */} -

    Follow the set up guide for each server to - receive build and deployment data.

    +

    Jenkins for Jira lets your teams keep track of code + they build and deploy on Jenkins servers.

    +

    + To receive build and deployment data, your teams must follow the  + {/* TODO - ARC-2736 IPH component for 'set up guide' */} + set up guide for each server connected. +

    diff --git a/app/jenkins-for-jira-ui/src/components/icons/ConnectionPendingIcon.tsx b/app/jenkins-for-jira-ui/src/components/icons/ConnectionPendingIcon.tsx new file mode 100644 index 00000000..300a6bf0 --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/icons/ConnectionPendingIcon.tsx @@ -0,0 +1,106 @@ +import React from 'react'; + +export function ConnectionPendingIcon(): JSX.Element { + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + ); +} diff --git a/app/jenkins-for-jira-ui/src/components/icons/DuplicateServerIcon.tsx b/app/jenkins-for-jira-ui/src/components/icons/DuplicateServerIcon.tsx new file mode 100644 index 00000000..fa0bbbfe --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/icons/DuplicateServerIcon.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +export function DuplicateServerIcon(): JSX.Element { + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + ); +} diff --git a/app/jenkins-for-jira-ui/src/components/icons/NoDataIcon.tsx b/app/jenkins-for-jira-ui/src/components/icons/NoDataIcon.tsx new file mode 100644 index 00000000..747761cf --- /dev/null +++ b/app/jenkins-for-jira-ui/src/components/icons/NoDataIcon.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +export function NoDataIcon(): JSX.Element { + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + ); +} diff --git a/app/jenkins-for-jira-ui/src/components/icons/UpdateAvailableIcon.tsx b/app/jenkins-for-jira-ui/src/components/icons/UpdateAvailableIcon.tsx index b21e745c..51850992 100644 --- a/app/jenkins-for-jira-ui/src/components/icons/UpdateAvailableIcon.tsx +++ b/app/jenkins-for-jira-ui/src/components/icons/UpdateAvailableIcon.tsx @@ -9,8 +9,8 @@ export const UpdateAvailableIcon = ({ containerClassName }: UpdateAvailableIconP return (
    - - + + @@ -32,25 +32,25 @@ export const UpdateAvailableIcon = ({ containerClassName }: UpdateAvailableIconP - - - + + + - - + + - - + + - - + + - - + + diff --git a/app/src/common/types.ts b/app/src/common/types.ts index 27e18024..eb59db5c 100644 --- a/app/src/common/types.ts +++ b/app/src/common/types.ts @@ -19,7 +19,8 @@ export interface JenkinsServer { secret?: string, pipelines: JenkinsPipeline[], pluginConfig?: JenkinsPluginConfig, - connectedState?: ConnectedState + connectedState?: ConnectedState, + originalConnection?: string } export interface JenkinsPipeline {