diff --git a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx
index 9f51d4e28cd..836d3aea0e2 100644
--- a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx
+++ b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx
@@ -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 {
@@ -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', {
@@ -54,6 +71,16 @@ const TeamPromptWorkDrawer = (props: Props) => {
label: 'Parabol',
Component: ParabolTasksPanel
},
+ ...(hasJiraServer
+ ? [
+ {
+ icon: ,
+ service: 'jiraServer',
+ label: 'Jira Server',
+ Component: JiraServerIntegrationPanel
+ }
+ ]
+ : []),
{icon: , service: 'github', label: 'GitHub', Component: GitHubIntegrationPanel},
{icon: , service: 'jira', label: 'Jira', Component: JiraIntegrationPanel},
{
diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx
new file mode 100644
index 00000000000..da6eedf1a05
--- /dev/null
+++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx
@@ -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 ? (
+
+ ) : (
+
+
+
+
+
Connect to Jira Server
+
+ Connect to Jira Server to view your issues.
+
+
+ {error &&
Error: {error.message}
}
+
+ )}
+ >
+ )
+}
+
+export default JiraServerIntegrationPanel
diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx
new file mode 100644
index 00000000000..3beed1d4c9c
--- /dev/null
+++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx
@@ -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
+ 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 (
+ <>
+
+ {jiraResults && jiraResults.length > 0 ? (
+ jiraResults?.map((result, idx) => {
+ if (!result) {
+ return null
+ }
+ return
+ })
+ ) : (
+
+
+
+ {error?.message ? error.message : `Looks like you don’t have any issues to display.`}
+
+
+ Review your Jira Server configuration
+
+
+ )}
+ {lastItem}
+ {hasNext && (
+
+
+
+ )}
+
+ >
+ )
+}
+
+export default JiraServerIntegrationResults
diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx
new file mode 100644
index 00000000000..c58e4170e3d
--- /dev/null
+++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx
@@ -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(
+ jiraIntegrationResultsQuery,
+ {
+ teamId: teamId
+ }
+ )
+ return (
+
+ }>
+ {queryRef && }
+
+
+ )
+}
+
+export default JiraServerIntegrationResultsRoot
diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx
new file mode 100644
index 00000000000..dd6da759a71
--- /dev/null
+++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx
@@ -0,0 +1,121 @@
+import {Link} from '@mui/icons-material'
+import graphql from 'babel-plugin-relay/macro'
+import React from 'react'
+import CopyToClipboard from 'react-copy-to-clipboard'
+import {useFragment} from 'react-relay'
+import {JiraServerObjectCard_result$key} from '../../../__generated__/JiraServerObjectCard_result.graphql'
+import useAtmosphere from '../../../hooks/useAtmosphere'
+import {MenuPosition} from '../../../hooks/useCoords'
+import useTooltip from '../../../hooks/useTooltip'
+import jiraSVG from '../../../styles/theme/images/graphics/jira.svg'
+import SendClientSideEvent from '../../../utils/SendClientSideEvent'
+import relativeDate from '../../../utils/date/relativeDate'
+import {mergeRefs} from '../../../utils/react/mergeRefs'
+
+interface Props {
+ resultRef: JiraServerObjectCard_result$key
+}
+
+const JiraServerObjectCard = (props: Props) => {
+ const {resultRef} = props
+
+ const result = useFragment(
+ graphql`
+ fragment JiraServerObjectCard_result on JiraServerIssue {
+ id
+ summary
+ url
+ issueKey
+ projectKey
+ projectName
+ updatedAt
+ }
+ `,
+ resultRef
+ )
+
+ const atmosphere = useAtmosphere()
+
+ const {tooltipPortal, openTooltip, closeTooltip, originRef} = useTooltip(
+ MenuPosition.UPPER_CENTER
+ )
+
+ const {
+ tooltipPortal: copiedTooltipPortal,
+ openTooltip: openCopiedTooltip,
+ closeTooltip: closeCopiedTooltip,
+ originRef: copiedTooltipRef
+ } = useTooltip(MenuPosition.LOWER_CENTER)
+
+ const trackLinkClick = () => {
+ SendClientSideEvent(atmosphere, 'Your Work Drawer Card Link Clicked', {
+ service: 'jira'
+ })
+ }
+
+ const trackCopy = () => {
+ SendClientSideEvent(atmosphere, 'Your Work Drawer Card Copied', {
+ service: 'jira'
+ })
+ }
+
+ const handleCopy = () => {
+ openCopiedTooltip()
+ trackCopy()
+ setTimeout(() => {
+ closeCopiedTooltip()
+ }, 2000)
+ }
+
+ const {summary, url, issueKey, projectName, updatedAt} = result
+
+ return (
+
+
+
+
+
+
+
+
+
{projectName}
+
+
+
+
+
+
+ {tooltipPortal('Copy link')}
+ {copiedTooltipPortal('Copied!')}
+
+
+ )
+}
+
+export default JiraServerObjectCard
diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql
index a5aa1e4ff48..406fd1d39c0 100644
--- a/packages/server/graphql/private/typeDefs/_legacy.graphql
+++ b/packages/server/graphql/private/typeDefs/_legacy.graphql
@@ -1095,6 +1095,7 @@ type JiraServerIssue implements TaskIntegration {
id: ID!
issueKey: ID!
projectKey: ID!
+ projectName: String!
"""
The parabol teamId this issue was fetched for
@@ -1121,6 +1122,11 @@ type JiraServerIssue implements TaskIntegration {
The description converted into raw HTML
"""
descriptionHTML: String!
+
+ """
+ The timestamp the issue was last updated
+ """
+ updatedAt: DateTime!
}
"""
diff --git a/packages/server/graphql/types/JiraServerIntegration.ts b/packages/server/graphql/types/JiraServerIntegration.ts
index 2f4f3968b74..ec349fa98b1 100644
--- a/packages/server/graphql/types/JiraServerIntegration.ts
+++ b/packages/server/graphql/types/JiraServerIntegration.ts
@@ -92,7 +92,7 @@ const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: str
},
after: {
type: GraphQLString,
- defaultValue: '0'
+ defaultValue: '-1'
},
queryString: {
type: GraphQLString,
@@ -162,21 +162,22 @@ const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: str
const {issues} = issueRes
const mappedIssues = issues.map((issue) => {
- const {project, issuetype, summary, description} = issue.fields
+ const {project, issuetype, summary, description, updated} = issue.fields
return {
...issue,
userId,
teamId,
providerId: provider.id,
issueKey: issue.key,
+ description: description ?? '',
descriptionHTML: issue.renderedFields.description,
projectId: project.id,
projectKey: project.key,
+ projectName: project.name,
issueType: issuetype.id,
summary,
- description,
service: 'jiraServer' as const,
- updatedAt: new Date()
+ updatedAt: new Date(updated)
}
})
diff --git a/packages/server/graphql/types/JiraServerIssue.ts b/packages/server/graphql/types/JiraServerIssue.ts
index 6d4a583c68a..3047f751c28 100644
--- a/packages/server/graphql/types/JiraServerIssue.ts
+++ b/packages/server/graphql/types/JiraServerIssue.ts
@@ -3,6 +3,7 @@ import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId'
import {JiraServerIssue as JiraServerRestIssue} from '../../dataloader/jiraServerLoaders'
import connectionDefinitions from '../connectionDefinitions'
import {GQLContext} from '../graphql'
+import GraphQLISO8601Type from './GraphQLISO8601Type'
import StandardMutationError from './StandardMutationError'
import TaskIntegration from './TaskIntegration'
@@ -40,6 +41,9 @@ const JiraServerIssue = new GraphQLObjectType
projectKey: {
type: new GraphQLNonNull(GraphQLID)
},
+ projectName: {
+ type: new GraphQLNonNull(GraphQLString)
+ },
teamId: {
type: new GraphQLNonNull(GraphQLID),
description: 'The parabol teamId this issue was fetched for'
@@ -84,6 +88,10 @@ const JiraServerIssue = new GraphQLObjectType
.map(({name}) => name)
return fieldNames
}
+ },
+ updatedAt: {
+ type: new GraphQLNonNull(GraphQLISO8601Type),
+ description: 'The timestamp the issue was last updated'
}
})
})
diff --git a/packages/server/integrations/jiraServer/JiraServerRestManager.ts b/packages/server/integrations/jiraServer/JiraServerRestManager.ts
index 52416b07746..36b024379a1 100644
--- a/packages/server/integrations/jiraServer/JiraServerRestManager.ts
+++ b/packages/server/integrations/jiraServer/JiraServerRestManager.ts
@@ -57,7 +57,9 @@ export interface JiraServerIssue {
id: string
key: string
name: string
+ self: string
}
+ updated: string
}
renderedFields: {
description: string