diff --git a/codegen.json b/codegen.json
index f8d59351943..1874bb6f837 100644
--- a/codegen.json
+++ b/codegen.json
@@ -1,16 +1,17 @@
{
- "schema": "packages/server/utils/githubSchema.graphql",
- "documents": "packages/server/utils/githubQueries/*.graphql",
+ "config": {
+ "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck"
+ },
"generates": {
"packages/server/types/githubTypes.ts": {
- "plugins": [
- "typescript",
- "typescript-operations",
- "add"
- ],
- "config": {
- "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck"
- }
+ "schema": "packages/server/utils/githubSchema.graphql",
+ "documents": "packages/server/utils/githubQueries/*.graphql",
+ "plugins": ["typescript", "typescript-operations", "add"]
+ },
+ "packages/server/types/gitlabTypes.ts": {
+ "schema": "packages/server/graphql/nestedSchema/GitLab/gitlabSchema.graphql",
+ "documents": "packages/server/graphql/nestedSchema/GitLab/queries/*.graphql",
+ "plugins": ["typescript", "typescript-operations", "add"]
}
}
}
diff --git a/packages/client/components/GitLabProviderLogo.tsx b/packages/client/components/GitLabProviderLogo.tsx
new file mode 100644
index 00000000000..62f5f933b23
--- /dev/null
+++ b/packages/client/components/GitLabProviderLogo.tsx
@@ -0,0 +1,10 @@
+import styled from '@emotion/styled'
+import logo from '../styles/theme/images/graphics/gitlab-icon-rgb.svg'
+
+const GitLabProviderLogo = styled('div')({
+ background: `url("${logo}")`,
+ height: 48,
+ width: 48
+})
+
+export default GitLabProviderLogo
diff --git a/packages/client/components/GitLabSVG.tsx b/packages/client/components/GitLabSVG.tsx
new file mode 100644
index 00000000000..ee2d4044cbc
--- /dev/null
+++ b/packages/client/components/GitLabSVG.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+
+const GitLabSVG = React.memo(() => {
+ return (
+
+ )
+})
+
+export default GitLabSVG
diff --git a/packages/client/hooks/useMenu.ts b/packages/client/hooks/useMenu.ts
index 9102c6c724e..e551f169145 100644
--- a/packages/client/hooks/useMenu.ts
+++ b/packages/client/hooks/useMenu.ts
@@ -33,7 +33,15 @@ const useMenu = (
if (originCoords) {
;(originRef as any).current = {getBoundingClientRect: () => originCoords} as RectElement
}
- const {portal, closePortal, togglePortal, portalStatus, setPortalStatus, openPortal} = usePortal({
+ const {
+ portal,
+ closePortal,
+ openPortal,
+ portalStatus,
+ terminatePortal,
+ togglePortal,
+ setPortalStatus
+ } = usePortal({
id,
onOpen,
onClose,
@@ -60,14 +68,15 @@ const useMenu = (
)
const menuProps = {portalStatus, closePortal, isDropdown}
return {
- togglePortal,
- originRef,
- menuPortal,
- menuProps,
loadingDelay,
loadingWidth,
+ menuPortal,
+ menuProps,
+ openPortal,
+ originRef,
portalStatus,
- openPortal
+ terminatePortal,
+ togglePortal
}
}
diff --git a/packages/client/modules/demo/DemoUser.ts b/packages/client/modules/demo/DemoUser.ts
index 82841f69519..412cad1ff1e 100644
--- a/packages/client/modules/demo/DemoUser.ts
+++ b/packages/client/modules/demo/DemoUser.ts
@@ -8,7 +8,8 @@ export default class DemoUser {
createdAt = new Date().toJSON()
email: string
featureFlags = {
- jira: false
+ gitlab: false,
+ video: false
}
facilitatorUserId: string
facilitatorName: string
diff --git a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx
index 8b90ce6de7c..5bf1dca0354 100644
--- a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx
@@ -6,6 +6,7 @@ import graphql from 'babel-plugin-relay/macro'
import SettingsWrapper from '../../../../components/Settings/SettingsWrapper'
import AtlassianProviderRow from '../ProviderRow/AtlassianProviderRow'
import GitHubProviderRow from '../ProviderRow/GitHubProviderRow'
+import GitLabProviderRow from '../ProviderRow/GitLabProviderRow'
import MattermostProviderRow from '../ProviderRow/MattermostProviderRow'
import SlackProviderRow from '../ProviderRow/SlackProviderRow'
@@ -21,10 +22,14 @@ const StyledWrapper = styled(SettingsWrapper)({
const ProviderList = (props: Props) => {
const {viewer, retry, teamId} = props
+ const {
+ featureFlags: {gitlab: allowGitlab}
+ } = viewer
return (
+ {allowGitlab && }
@@ -36,8 +41,13 @@ export default createFragmentContainer(ProviderList, {
fragment ProviderList_viewer on User {
...AtlassianProviderRow_viewer
...GitHubProviderRow_viewer
+ ...GitLabProviderRow_viewer
...MattermostProviderRow_viewer
...SlackProviderRow_viewer
+
+ featureFlags {
+ gitlab
+ }
}
`
})
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/GitLabConfigMenu.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabConfigMenu.tsx
new file mode 100644
index 00000000000..81dc0a57a35
--- /dev/null
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabConfigMenu.tsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import useAtmosphere from '../../../../hooks/useAtmosphere'
+import {MenuProps} from '../../../../hooks/useMenu'
+import {MenuMutationProps} from '../../../../hooks/useMutationProps'
+import RemoveIntegrationTokenMutation from '../../../../mutations/RemoveIntegrationTokenMutation'
+import Menu from '../../../../components/Menu'
+import MenuItem from '../../../../components/MenuItem'
+
+interface Props {
+ menuProps: MenuProps
+ mutationProps: MenuMutationProps
+ providerId: string
+ teamId: string
+ terminatePortal: () => void
+}
+
+const GitLabConfigMenu = (props: Props) => {
+ const {menuProps, mutationProps, providerId, teamId, terminatePortal} = props
+ const {onError, onCompleted, submitMutation, submitting} = mutationProps
+ const atmosphere = useAtmosphere()
+
+ const removeGitLabAuth = () => {
+ if (submitting) return
+ submitMutation()
+ RemoveIntegrationTokenMutation(atmosphere, {providerId, teamId}, {onCompleted, onError})
+ // Our parent component does not unmount, and it often re-renders before the CSS menu transition
+ // can complete. We nuke the portal here to ensure the menu is closed.
+ terminatePortal()
+ }
+ return (
+
+ )
+}
+
+export default GitLabConfigMenu
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx
new file mode 100644
index 00000000000..b1394fe7046
--- /dev/null
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx
@@ -0,0 +1,223 @@
+import styled from '@emotion/styled'
+import graphql from 'babel-plugin-relay/macro'
+import React from 'react'
+import {useFragment} from 'react-relay'
+import useAtmosphere from '../../../../hooks/useAtmosphere'
+import useMutationProps from '../../../../hooks/useMutationProps'
+import FlatButton from '../../../../components/FlatButton'
+import GitLabProviderLogo from '../../../../components/GitLabProviderLogo'
+import GitLabSVG from '../../../../components/GitLabSVG'
+import Icon from '../../../../components/Icon'
+import ProviderActions from '../../../../components/ProviderActions'
+import ProviderCard from '../../../../components/ProviderCard'
+import RowInfo from '../../../../components/Row/RowInfo'
+import RowInfoCopy from '../../../../components/Row/RowInfoCopy'
+import useBreakpoint from '../../../../hooks/useBreakpoint'
+import {MenuPosition} from '../../../../hooks/useCoords'
+import useMenu from '../../../../hooks/useMenu'
+import {PALETTE} from '../../../../styles/paletteV3'
+import {ICON_SIZE} from '../../../../styles/typographyV2'
+import {Breakpoint} from '../../../../types/constEnums'
+import GitLabClientManager, {GitLabIntegrationProvider} from '../../../../utils/GitLabClientManager'
+import {GitLabProviderRow_viewer$key} from '../../../../__generated__/GitLabProviderRow_viewer.graphql'
+import GitLabConfigMenu from './GitLabConfigMenu'
+import useTooltip from 'parabol-client/hooks/useTooltip'
+
+const StyledButton = styled(FlatButton)({
+ color: PALETTE.SLATE_700,
+ fontSize: 14,
+ fontWeight: 600,
+ minWidth: 36,
+ paddingLeft: 0,
+ paddingRight: 0,
+ width: '100%'
+})
+
+const StyledPrimaryButton = styled(StyledButton)({
+ borderColor: PALETTE.SLATE_400
+})
+
+const StyledSecondaryButton = styled(StyledButton)({
+ backgroundColor: PALETTE.SLATE_200,
+ marginLeft: 16
+})
+
+interface Props {
+ teamId: string
+ viewerRef: GitLabProviderRow_viewer$key
+}
+
+const MenuButton = styled(FlatButton)({
+ color: PALETTE.GRAPE_700,
+ fontSize: ICON_SIZE.MD18,
+ height: 24,
+ userSelect: 'none',
+ marginLeft: 4,
+ padding: 0,
+ width: 24
+})
+
+const StyledIcon = styled(Icon)({
+ fontSize: ICON_SIZE.MD18
+})
+
+const ListAndMenu = styled('div')({
+ display: 'flex',
+ position: 'absolute',
+ right: 16,
+ top: 16
+})
+
+const GitLabLogin = styled('div')({})
+
+const ProviderName = styled('div')({
+ color: PALETTE.SLATE_700,
+ fontSize: 16,
+ fontWeight: 600,
+ lineHeight: '24px',
+ alignItems: 'center',
+ display: 'flex',
+ marginRight: 16,
+ verticalAlign: 'middle'
+})
+
+const GitLabProviderRow = (props: Props) => {
+ const {teamId, viewerRef} = props
+ const viewer = useFragment(
+ graphql`
+ fragment GitLabProviderRow_viewer on User {
+ teamMember(teamId: $teamId) {
+ integrations {
+ gitlab {
+ availableProviders {
+ id
+ scope
+ type
+ name
+ updatedAt
+ providerMetadata {
+ ... on OAuth2ProviderMetadata {
+ clientId
+ serverBaseUrl
+ scopes
+ }
+ }
+ }
+ activeProvider {
+ id
+ name
+ }
+ isActive
+ teamId
+ }
+ }
+ }
+ }
+ `,
+ viewerRef
+ )
+ const {teamMember} = viewer
+ const {integrations} = teamMember!
+ const {gitlab} = integrations
+ const {isActive, availableProviders} = gitlab!
+ const atmosphere = useAtmosphere()
+ const mutationProps = useMutationProps()
+ const {submitting} = mutationProps
+ const primaryProvider = GitLabClientManager.getPrimaryProvider(availableProviders)
+ const secondaryProvider = GitLabClientManager.getSecondaryProvider(availableProviders)
+ const openOAuth = (provider: GitLabIntegrationProvider) => {
+ GitLabClientManager.openOAuth(atmosphere, provider, teamId, mutationProps)
+ }
+ const {
+ originRef: menuRef,
+ menuPortal,
+ menuProps,
+ terminatePortal,
+ togglePortal
+ } = useMenu(MenuPosition.UPPER_RIGHT)
+ const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT)
+ const primaryProviderName = !secondaryProvider ? 'Connect' : primaryProvider!.name
+ const {
+ tooltipPortal: primaryTooltipPortal,
+ openTooltip: primaryOpenTooltip,
+ closeTooltip: primaryCloseTooltip,
+ originRef: primaryRef
+ } = useTooltip(MenuPosition.LOWER_CENTER)
+ const {
+ tooltipPortal: secondaryTooltipPortal,
+ openTooltip: secondaryOpenTooltip,
+ closeTooltip: secondaryCloseTooltip,
+ originRef: secondaryRef
+ } = useTooltip(MenuPosition.LOWER_CENTER)
+
+ return (
+
+
+
+ GitLab
+ Use GitLab Issues from within Parabol
+
+ {!isActive && (
+
+ {primaryProvider && (
+ openOAuth(primaryProvider)}
+ palette='warm'
+ waiting={submitting}
+ onMouseOver={primaryOpenTooltip}
+ onMouseOut={primaryCloseTooltip}
+ ref={primaryRef as any}
+ >
+ {isDesktop ? primaryProviderName : add}
+
+ )}
+ {primaryProvider && primaryTooltipPortal('Connect to GitLab Cloud')}
+ {secondaryProvider && (
+ openOAuth(secondaryProvider)}
+ palette='warm'
+ waiting={submitting}
+ onMouseOver={secondaryOpenTooltip}
+ onMouseOut={secondaryCloseTooltip}
+ ref={secondaryRef as any}
+ >
+ {isDesktop ? (
+ GitLabClientManager.getTruncatedProviderName(secondaryProvider.name)
+ ) : (
+ enhanced_encryption
+ )}
+
+ )}
+ {secondaryProvider &&
+ secondaryTooltipPortal(
+ `Connect to ${secondaryProvider!.providerMetadata!.serverBaseUrl}`
+ )}
+
+ )}
+ {isActive && (
+
+ {/* */}
+
+
+
+
+ more_vert
+
+ {menuPortal(
+
+ )}
+
+ )}
+
+ )
+}
+
+export default GitLabProviderRow
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostConfigMenu.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostConfigMenu.tsx
index cd5121d5872..cc131170e8c 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostConfigMenu.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostConfigMenu.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import useAtmosphere from '../../../../hooks/useAtmosphere'
import {MenuProps} from '../../../../hooks/useMenu'
import {MenuMutationProps} from '../../../../hooks/useMutationProps'
-import RemoveMattermostAuthMutation from '../../../../mutations/RemoveMattermostAuthMutation'
+import RemoveIntegrationProviderMutation from '../../../../mutations/RemoveIntegrationProviderMutation'
import Menu from '../../../../components/Menu'
import MenuItem from '../../../../components/MenuItem'
@@ -10,17 +10,22 @@ interface Props {
menuProps: MenuProps
mutationProps: MenuMutationProps
teamId: string
+ providerId: string
+ terminatePortal: () => void
}
const MattermostConfigMenu = (props: Props) => {
- const {menuProps, mutationProps, teamId} = props
+ const {menuProps, mutationProps, providerId, teamId, terminatePortal} = props
const {onError, onCompleted, submitMutation, submitting} = mutationProps
const atmosphere = useAtmosphere()
const removeMattermostAuth = () => {
if (submitting) return
submitMutation()
- RemoveMattermostAuthMutation(atmosphere, {teamId}, {onCompleted, onError})
+ RemoveIntegrationProviderMutation(atmosphere, {providerId, teamId}, {onCompleted, onError})
+ // Our parent component does not unmount, and it often re-renders before the CSS menu transition
+ // can complete. We nuke the portal here to ensure the menu is closed.
+ terminatePortal()
}
return (