Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Jira Server to Your Work #9794

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import gcalLogo from '../../styles/theme/images/graphics/google-calendar.svg'
import SendClientSideEvent from '../../utils/SendClientSideEvent'
import GitHubSVG from '../GitHubSVG'
import JiraSVG from '../JiraSVG'
import JiraServerSVG from '../JiraServerSVG'
import ParabolLogoSVG from '../ParabolLogoSVG'
import Tab from '../Tab/Tab'
import Tabs from '../Tabs/Tabs'
import GCalIntegrationPanel from './WorkDrawer/GCalIntegrationPanel'
import GitHubIntegrationPanel from './WorkDrawer/GitHubIntegrationPanel'
import JiraIntegrationPanel from './WorkDrawer/JiraIntegrationPanel'
import JiraServerIntegrationPanel from './WorkDrawer/JiraServerIntegrationPanel'
import ParabolTasksPanel from './WorkDrawer/ParabolTasksPanel'

interface Props {
Expand All @@ -32,11 +34,26 @@ const TeamPromptWorkDrawer = (props: Props) => {
...GitHubIntegrationPanel_meeting
...JiraIntegrationPanel_meeting
...GCalIntegrationPanel_meeting
...JiraServerIntegrationPanel_meeting
viewerMeetingMember {
teamMember {
teamId
integrations {
jiraServer {
sharedProviders {
id
}
}
}
}
}
}
`,
meetingRef
)
const atmosphere = useAtmosphere()
const hasJiraServer =
!!meeting.viewerMeetingMember?.teamMember?.integrations.jiraServer?.sharedProviders?.length

useEffect(() => {
SendClientSideEvent(atmosphere, 'Your Work Drawer Impression', {
Expand All @@ -54,6 +71,16 @@ const TeamPromptWorkDrawer = (props: Props) => {
label: 'Parabol',
Component: ParabolTasksPanel
},
...(hasJiraServer
? [
{
icon: <JiraServerSVG />,
service: 'jiraServer',
label: 'Jira Server',
Component: JiraServerIntegrationPanel
}
]
: []),
{icon: <GitHubSVG />, service: 'github', label: 'GitHub', Component: GitHubIntegrationPanel},
{icon: <JiraSVG />, service: 'jira', label: 'Jira', Component: JiraIntegrationPanel},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import {JiraServerIntegrationPanel_meeting$key} from '../../../__generated__/JiraServerIntegrationPanel_meeting.graphql'
import useAtmosphere from '../../../hooks/useAtmosphere'
import useMutationProps from '../../../hooks/useMutationProps'
import jiraServerSVG from '../../../styles/theme/images/graphics/jira-software-blue.svg'
import JiraServerClientManager from '../../../utils/JiraServerClientManager'
import SendClientSideEvent from '../../../utils/SendClientSideEvent'
import JiraServerIntegrationResultsRoot from './JiraServerIntegrationResultsRoot'

interface Props {
meetingRef: JiraServerIntegrationPanel_meeting$key
}

const JiraServerIntegrationPanel = (props: Props) => {
const {meetingRef} = props
const meeting = useFragment(
graphql`
fragment JiraServerIntegrationPanel_meeting on TeamPromptMeeting {
id
teamId
viewerMeetingMember {
teamMember {
teamId
integrations {
jiraServer {
auth {
id
isActive
}
sharedProviders {
id
}
}
}
}
}
}
`,
meetingRef
)

const teamMember = meeting.viewerMeetingMember?.teamMember
const integration = teamMember?.integrations.jiraServer
const providerId = integration?.sharedProviders?.[0]?.id
const isActive = !!integration?.auth?.isActive

const atmosphere = useAtmosphere()
const mutationProps = useMutationProps()
const {error, onError} = mutationProps

const authJiraServer = () => {
if (!teamMember || !providerId) {
return onError(new Error('Could not find integration provider'))
}
JiraServerClientManager.openOAuth(atmosphere, providerId, teamMember.teamId, mutationProps)

SendClientSideEvent(atmosphere, 'Your Work Drawer Integration Connected', {
teamId: meeting.teamId,
meetingId: meeting.id,
service: 'jira server'
})
}
if (!teamMember || !teamMember) {
return null
}

return (
<>
{isActive ? (
<JiraServerIntegrationResultsRoot teamId={teamMember.teamId} />
) : (
<div className='-mt-14 flex h-full flex-col items-center justify-center gap-2'>
<div className='h-10 w-10'>
<img className='h-10 w-10' src={jiraServerSVG} />
</div>
<b>Connect to Jira Server</b>
<div className='w-1/2 text-center text-sm'>
Connect to Jira Server to view your issues.
</div>
<button
className='mt-4 cursor-pointer rounded-full bg-sky-500 px-8 py-2 font-semibold text-white hover:bg-sky-600'
onClick={authJiraServer}
>
Connect
</button>
{error && <div className='text-tomato-500'>Error: {error.message}</div>}
</div>
)}
</>
)
}

export default JiraServerIntegrationPanel
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {PreloadedQuery, usePaginationFragment, usePreloadedQuery} from 'react-relay'
import {Link} from 'react-router-dom'
import halloweenRetrospectiveTemplate from '../../../../../static/images/illustrations/halloweenRetrospectiveTemplate.png'
import {JiraServerIntegrationResultsQuery} from '../../../__generated__/JiraServerIntegrationResultsQuery.graphql'
import {JiraServerIntegrationResultsSearchPaginationQuery} from '../../../__generated__/JiraServerIntegrationResultsSearchPaginationQuery.graphql'
import {JiraServerIntegrationResults_search$key} from '../../../__generated__/JiraServerIntegrationResults_search.graphql'
import useLoadNextOnScrollBottom from '../../../hooks/useLoadNextOnScrollBottom'
import Ellipsis from '../../Ellipsis/Ellipsis'
import JiraServerObjectCard from './JiraServerObjectCard'

interface Props {
queryRef: PreloadedQuery<JiraServerIntegrationResultsQuery>
teamId: string
}

const JiraServerIntegrationResults = (props: Props) => {
const {queryRef, teamId} = props
const query = usePreloadedQuery(
graphql`
query JiraServerIntegrationResultsQuery($teamId: ID!) {
...JiraServerIntegrationResults_search @arguments(teamId: $teamId)
}
`,
queryRef
)

const paginationRes = usePaginationFragment<
JiraServerIntegrationResultsSearchPaginationQuery,
JiraServerIntegrationResults_search$key
>(
graphql`
fragment JiraServerIntegrationResults_search on Query
@argumentDefinitions(
cursor: {type: "String"}
count: {type: "Int", defaultValue: 20}
teamId: {type: "ID!"}
)
@refetchable(queryName: "JiraServerIntegrationResultsSearchPaginationQuery") {
viewer {
teamMember(teamId: $teamId) {
integrations {
jiraServer {
issues(
first: $count
after: $cursor
isJQL: true
queryString: "assignee = currentUser() order by updated DESC"
) @connection(key: "JiraServerScopingSearchResults_issues") {
error {
message
}
edges {
node {
...JiraServerObjectCard_result
id
summary
url
issueKey
}
}
}
}
}
}
}
}
`,
query
)

const lastItem = useLoadNextOnScrollBottom(paginationRes, {}, 20)
const {data, hasNext} = paginationRes

const jira = data.viewer.teamMember?.integrations.jiraServer
const jiraResults = jira?.issues.edges.map((edge) => edge.node)
const error = jira?.issues.error ?? null

return (
<>
<div className='flex flex h-full flex-col gap-y-2 overflow-auto p-4'>
{jiraResults && jiraResults.length > 0 ? (
jiraResults?.map((result, idx) => {
if (!result) {
return null
}
return <JiraServerObjectCard key={idx} resultRef={result} />
})
) : (
<div className='-mt-14 flex h-full flex-col items-center justify-center'>
<img className='w-20' src={halloweenRetrospectiveTemplate} />
<div className='mt-7 w-2/3 text-center'>
{error?.message ? error.message : `Looks like you don’t have any issues to display.`}
</div>
<Link
to={`/team/${teamId}/integrations`}
className='mt-4 font-semibold text-sky-500 hover:text-sky-400'
>
Review your Jira Server configuration
</Link>
</div>
)}
{lastItem}
{hasNext && (
<div className='mx-auto mb-4 -mt-4 h-8 text-2xl' key={'loadingNext'}>
<Ellipsis />
</div>
)}
</div>
</>
)
}

export default JiraServerIntegrationResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, {Suspense} from 'react'
import {Loader} from '~/utils/relay/renderLoader'
import jiraIntegrationResultsQuery, {
JiraServerIntegrationResultsQuery
} from '../../../__generated__/JiraServerIntegrationResultsQuery.graphql'
import useQueryLoaderNow from '../../../hooks/useQueryLoaderNow'
import ErrorBoundary from '../../ErrorBoundary'
import JiraServerIntegrationResults from './JiraServerIntegrationResults'

interface Props {
teamId: string
}

const JiraServerIntegrationResultsRoot = (props: Props) => {
const {teamId} = props
const queryRef = useQueryLoaderNow<JiraServerIntegrationResultsQuery>(
jiraIntegrationResultsQuery,
{
teamId: teamId
}
)
return (
<ErrorBoundary>
<Suspense fallback={<Loader />}>
{queryRef && <JiraServerIntegrationResults queryRef={queryRef} teamId={teamId} />}
</Suspense>
</ErrorBoundary>
)
}

export default JiraServerIntegrationResultsRoot
Loading
Loading