Skip to content

Commit

Permalink
feat(sprint-poker): Add GitLab issue (ParabolInc#6267)
Browse files Browse the repository at this point in the history
* render list of gitlab issues

* use usePaginationFragment

* increase default value for usePaginationFragment

* implement GitLabScopingSearchResultItem

* add GitLabScopingSelectAllIssues

* can add and remove gitlab issues in scope phase

* add UpdatePokerScopeMutation gitlab optimistic updater

* able to select all issues

* clean up type errors

* add projects name alias to rootSchema

* include projectIds in search query

* return iid from GitLabIssueId

* sort projects by lastActivityAt

* implement fetchGitLabProjects

* implement gitlab issue menu

* implement NewGitLabIssueMenuRoot instead of using useAllIntegrations

* load next if projects dont have any issues

* update comment

* remove gitlab menu root and use defaultProjects query to populate menu

* increase projectsFirst from 10 to 20

* query all gitlab projects

* chore(comment): how to extend BaseTaskIntegration

* able to create a new gitlab issue

* use GitLabServerManager and implement parseWebPath

* fix undefined baseUri

* changed taskIntegrationGitLab and GitLabId to use providerId

* lowercase gitlabRequest to be consistent with gh

* add handle create gitlab issue

* add info fragment and return data instead of data.issue

* add serverBaseUrl

* adjusted root schema and can now render projects in input menu again

* clean up update poker scope and create task gitlab

* fix ts errors

* add gitlab query types

* add fullPath to gitLab issue edge if exists

* get nodes appearing on insert

* get gitlab issue title in create task updater

* include webUrl in createTask query so user can click on newly create issue url

* add first and sort to allProjects query

* rename GitLabRepo to GitLabProject

* pass meetingId to issue input rather than querying it

* remove __typename and resolveTypes

* update UpdatePokerScopeMutation to fix selectAll bug

* clean-up return statement in fetchGitLabProjects

* merge with master

* use react-swipeable-views workaround

* add _xGitLabProject resolver

* merge with master

* map over tabs instead of contents

* fix(gitlab): add proper client-side alias handling (ParabolInc#6361)

* resolve to aliased fields

* resolve to aliased fields

* make poker input menu a dropdown and fix width

* refactor baseTabs to include Component

* create a single source of truth for gitlab issue args

* remove refetchable from gitlab scoping results query

Co-authored-by: Matt Krick <[email protected]>
  • Loading branch information
2 people authored and atannus committed Apr 26, 2022
1 parent 5c454df commit 3ed3fde
Show file tree
Hide file tree
Showing 31 changed files with 779 additions and 194 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ packages/server/types/githubTypes.ts
packages/server/types/gitlabTypes.ts
packages/server/graphql/private/resolverTypes.ts
packages/server/graphql/public/resolverTypes.ts
packages/server/types/gitlabTypes.ts
packages/server/types/githubTypes.ts
queryMap.json
rethinkdb_data/
rethinkdb_dump_*
Expand Down
5 changes: 4 additions & 1 deletion codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
},
"packages/server/types/gitlabTypes.ts": {
"schema": "packages/server/graphql/nestedSchema/GitLab/gitlabSchema.graphql",
"documents": "packages/server/graphql/nestedSchema/GitLab/queries/*.graphql",
"documents": [
"packages/server/graphql/nestedSchema/GitLab/queries/*.graphql",
"packages/server/graphql/nestedSchema/GitLab/mutations/*.graphql"
],
"plugins": ["typescript", "typescript-operations", "add"],
"config": {
"assumeValidSDL": true
Expand Down
12 changes: 5 additions & 7 deletions packages/client/components/GitHubScopingSearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,11 @@ const GitHubScopingSearchResults = (props: Props) => {
}
return (
<>
{
<GitHubScopingSelectAllIssues
usedServiceTaskIds={usedServiceTaskIds}
issuesRef={issues}
meetingId={meetingId}
/>
}
<GitHubScopingSelectAllIssues
usedServiceTaskIds={usedServiceTaskIds}
issuesRef={issues}
meetingId={meetingId}
/>
<ResultScroller>
{query && (
<NewGitHubIssueInput
Expand Down
108 changes: 24 additions & 84 deletions packages/client/components/GitLabScopingSearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React, {useState} from 'react'
import {PreloadedQuery, useFragment, usePaginationFragment, usePreloadedQuery} from 'react-relay'
import {PreloadedQuery, useFragment, usePreloadedQuery} from 'react-relay'
import useGetUsedServiceTaskIds from '~/hooks/useGetUsedServiceTaskIds'
import useLoadNextOnScrollBottom from '~/hooks/useLoadNextOnScrollBottom'
import MockScopingList from '~/modules/meeting/components/MockScopingList'
// import {GitLabScopingSearchResultsRoot_meeting$key} from '~/__generated__/GitLabScopingSearchResultsRoot_meeting.graphql'
// import useAtmosphere from '../hooks/useAtmosphere'
// import useGetUsedServiceTaskIds from '../hooks/useGetUsedServiceTaskIds'
// import useLoadNextOnScrollBottom from '../hooks/useLoadNextOnScrollBottom'
// import PersistGitLabSearchQueryMutation from '../mutations/PersistGitLabSearchQueryMutation'
// import {SprintPokerDefaults} from '../types/constEnums'
import getNonNullEdges from '../utils/getNonNullEdges'
import {GitLabScopingSearchResultsPaginationQuery} from '../__generated__/GitLabScopingSearchResultsPaginationQuery.graphql'
import {GitLabScopingSearchResultsQuery} from '../__generated__/GitLabScopingSearchResultsQuery.graphql'
import {GitLabScopingSearchResults_meeting$key} from '../__generated__/GitLabScopingSearchResults_meeting.graphql'
import {GitLabScopingSearchResults_query$key} from '../__generated__/GitLabScopingSearchResults_query.graphql'
import Ellipsis from './Ellipsis/Ellipsis'
// import Ellipsis from './Ellipsis/Ellipsis'
import GitLabScopingSearchResultItem from './GitLabScopingSearchResultItem'
import GitLabScopingSelectAllIssues from './GitLabScopingSelectAllIssues'
import IntegrationScopingNoResults from './IntegrationScopingNoResults'
// import NewGitLabIssueInput from './NewGitLabIssueInput'
import NewGitLabIssueInput from './NewGitLabIssueInput'
import NewIntegrationRecordButton from './NewIntegrationRecordButton'

const ResultScroller = styled('div')({
overflow: 'auto'
})

const LoadingNext = styled('div')({
display: 'flex',
height: 32,
fontSize: 24,
justifyContent: 'center',
width: '100%'
})

interface Props {
queryRef: PreloadedQuery<GitLabScopingSearchResultsQuery>
meetingRef: GitLabScopingSearchResults_meeting$key
Expand All @@ -45,30 +26,15 @@ const GitLabScopingSearchResults = (props: Props) => {
const {queryRef, meetingRef} = props
const query = usePreloadedQuery(
graphql`
query GitLabScopingSearchResultsQuery($teamId: ID!) {
...GitLabScopingSearchResults_query
}
`,
queryRef,
{UNSTABLE_renderPolicy: 'full'}
)

const paginationRes = usePaginationFragment<
GitLabScopingSearchResultsPaginationQuery,
GitLabScopingSearchResults_query$key
>(
graphql`
fragment GitLabScopingSearchResults_query on Query
@argumentDefinitions(
projectsFirst: {type: "Int", defaultValue: 20}
issuesFirst: {type: "Int", defaultValue: 25}
projectsAfter: {type: "String"}
issuesAfter: {type: "String"}
search: {type: "String"}
projectIds: {type: "[ID!]", defaultValue: null}
)
@refetchable(queryName: "GitLabScopingSearchResultsPaginationQuery") {
query GitLabScopingSearchResultsQuery(
$teamId: ID!
$first: Int!
$includeSubepics: Boolean!
$sort: _xGitLabIssueSort!
$state: _xGitLabIssuableState!
) {
viewer {
...NewGitLabIssueInput_viewer
teamMember(teamId: $teamId) {
integrations {
gitlab {
Expand All @@ -89,25 +55,21 @@ const GitLabScopingSearchResults = (props: Props) => {
query {
projects(
membership: true
first: $projectsFirst
after: $projectsAfter
first: 75
sort: "latest_activity_desc"
ids: null # $projectIds
ids: null # $selectedProjectsIds
) @connection(key: "GitLabScopingSearchResults_projects") {
edges {
node {
... on _xGitLabProject {
issues(
includeSubepics: true
state: opened
search: $search
sort: UPDATED_DESC
first: $issuesFirst
after: $issuesAfter
includeSubepics: $includeSubepics
state: $state
sort: $sort
first: $first
) {
edges {
node {
__typename
... on _xGitLabIssue {
...GitLabScopingSearchResultItem_issue
...GitLabScopingSelectAllIssues_issues
Expand All @@ -130,21 +92,16 @@ const GitLabScopingSearchResults = (props: Props) => {
}
}
`,
query
queryRef,
{UNSTABLE_renderPolicy: 'full'}
)

const lastItem = useLoadNextOnScrollBottom(paginationRes, {}, 20)
const {data, hasNext, loadNext} = paginationRes
const {viewer} = data
const {viewer} = query
const meeting = useFragment(
graphql`
fragment GitLabScopingSearchResults_meeting on PokerMeeting {
# ...NewGitLabIssueInput_meeting
id
teamId
# gitlabSearchQuery {
# queryString
# }
phases {
...useGetUsedServiceTaskIds_phase
phaseType
Expand All @@ -157,27 +114,17 @@ const GitLabScopingSearchResults = (props: Props) => {
const {integrations} = teamMember
const {gitlab} = integrations
const {id: meetingId, phases} = meeting
// const {queryString} = gitlabSearchQuery
const errors = gitlab?.api?.errors ?? null
const providerId = gitlab.auth!.provider.id
const nullableEdges = gitlab?.api?.query?.projects?.edges?.flatMap(
(project) => project?.node?.issues?.edges ?? null
)
const issues = nullableEdges
? getNonNullEdges(nullableEdges)
.filter((edge) => edge.node.__typename === '_xGitLabIssue')
.map(({node}) => node)
: null
const issues = nullableEdges ? getNonNullEdges(nullableEdges).map(({node}) => node) : null
const [isEditing, setIsEditing] = useState(false)
// const atmosphere = useAtmosphere()
const estimatePhase = phases.find(({phaseType}) => phaseType === 'ESTIMATE')!
const usedServiceTaskIds = useGetUsedServiceTaskIds(estimatePhase)
const handleAddIssueClick = () => setIsEditing(true)

// gitlab bug: a server error is returned and query is null when there are more projects available. For me, if the projectsFirst arg is <16, an error is returned and loadNext is required. See: https://github.com/ParabolInc/parabol/pull/6160#discussion_r826871705
if (gitlab?.api?.query === null) {
loadNext(20)
}
if (!issues) return <MockScopingList />
if (issues.length === 0 && !isEditing) {
return (
Expand All @@ -199,30 +146,23 @@ const GitLabScopingSearchResults = (props: Props) => {
providerId={providerId}
/>
<ResultScroller>
{/* {query && (
{query && (
<NewGitLabIssueInput
isEditing={isEditing}
meetingRef={meeting}
meetingId={meetingId}
setIsEditing={setIsEditing}
viewerRef={query.viewer}
viewerRef={viewer}
/>
)} */}
)}
{issues.map((issue) => (
<GitLabScopingSearchResultItem
key={issue.id}
issueRef={issue}
meetingId={meetingId}
usedServiceTaskIds={usedServiceTaskIds}
providerId={providerId}
// persistQuery={persistQuery}
/>
))}
{lastItem}
{hasNext && (
<LoadingNext key={'loadingNext'}>
<Ellipsis />
</LoadingNext>
)}
</ResultScroller>
{!isEditing && (
<NewIntegrationRecordButton onClick={handleAddIssueClick} labelText={'New Issue'} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ interface Props {
meetingRef: GitLabScopingSearchResultsRoot_meeting$key
}

export const gitlabIssueArgs = {
first: 25,
includeSubepics: true,
sort: 'UPDATED_DESC',
state: 'opened'
} as const

const GitLabScopingSearchResultsRoot = (props: Props) => {
const {meetingRef} = props
const meeting = useFragment(
Expand All @@ -33,7 +40,7 @@ const GitLabScopingSearchResultsRoot = (props: Props) => {
// const normalizedQueryString = queryString.trim()
const queryRef = useQueryLoaderNow<GitLabScopingSearchResultsQuery>(
gitlabScopingSearchResultsQuery,
{teamId}
{teamId, ...gitlabIssueArgs}
)
return (
<Suspense fallback={<MockScopingList />}>
Expand Down
20 changes: 10 additions & 10 deletions packages/client/components/MeetingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,6 @@ const MeetingCard = (props: Props) => {
const {meeting, status, onTransitionEnd, displayIdx} = props
const {name, team, id: meetingId, meetingType, phases} = meeting
const connectedUsers = useMeetingMemberAvatars(meeting)
if (!team) {
// 95% sure there's a bug in relay causing this
const errObj = {id: meetingId} as any
if (meeting.hasOwnProperty('team')) {
errObj.team = team
}
Sentry.captureException(new Error(`Missing Team on Meeting ${JSON.stringify(errObj)}`))
return null
}
const {id: teamId, name: teamName} = team
const meetingPhase = getMeetingPhase(phases)
const meetingPhaseLabel = (meetingPhase && phaseLabelLookup[meetingPhase.phaseType]) || 'Complete'
const maybeTabletPlus = useBreakpoint(Breakpoint.FUZZY_TABLET)
Expand All @@ -184,6 +174,16 @@ const MeetingCard = (props: Props) => {
closeTooltip,
originRef: tooltipRef
} = useTooltip<HTMLDivElement>(MenuPosition.UPPER_RIGHT)
if (!team) {
// 95% sure there's a bug in relay causing this
const errObj = {id: meetingId} as any
if (meeting.hasOwnProperty('team')) {
errObj.team = team
}
Sentry.captureException(new Error(`Missing Team on Meeting ${JSON.stringify(errObj)}`))
return null
}
const {id: teamId, name: teamName} = team

return (
<CardWrapper
Expand Down
11 changes: 6 additions & 5 deletions packages/client/components/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import styled from '@emotion/styled'
import React, {
forwardRef,
ReactNode,
RefObject,
useEffect,
useImperativeHandle,
useRef,
RefObject
useRef
} from 'react'
import styled from '@emotion/styled'
import MenuItemLabel from './MenuItemLabel'
import {PALETTE} from '../styles/paletteV3'
import MenuItemLabel from './MenuItemLabel'

export interface MenuItemProps {
isActive: boolean
Expand Down Expand Up @@ -49,7 +49,8 @@ const getIsHidden = (el: HTMLElement, parent: HTMLElement) => {
return parentBottom < isViewedThreshold
}
const MenuItem = forwardRef((props: Props, ref: any) => {
const {isDisabled, label, noCloseOnClick, onMouseEnter, onClick, onView, parentRef, dataCy} = props
const {isDisabled, label, noCloseOnClick, onMouseEnter, onClick, onView, parentRef, dataCy} =
props
const itemRef = useRef<HTMLDivElement>(null)
// we're doing something a little hacky here, overloading a callback ref with some props so we don't need to pass them explicitly
const {activate, closePortal, isActive} = ref as MenuItemProps
Expand Down
4 changes: 3 additions & 1 deletion packages/client/components/NewGitHubIssueInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const StyledButton = styled(PlainButton)({
justifyContent: 'flex-start',
margin: 0,
opacity: 1,
width: 'fit-content',
':hover, :focus': {
backgroundColor: 'transparent'
}
Expand Down Expand Up @@ -157,7 +158,8 @@ const NewGitHubIssueInput = (props: Props) => {
}
})
const {originRef, menuPortal, menuProps, togglePortal, portalStatus} = useMenu(
MenuPosition.UPPER_RIGHT
MenuPosition.UPPER_LEFT,
{isDropdown: true}
)
const ref = useRef<HTMLInputElement>(null)
const {dirty, error} = fields.newIssue
Expand Down
Loading

0 comments on commit 3ed3fde

Please sign in to comment.