Skip to content

Commit

Permalink
feat(sprint-poker): Track GitLab events (#6367)
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

* add gitlab search query

* filter by gitlab search query

* refactor to hooks

* query projects from project filter menu

* remove __typename and resolveTypes

* update UpdatePokerScopeMutation to fix selectAll bug

* render projects in filter menu

* use fullPath in gitlab menu and adjust max width

* refactor gitlab search query from string to object with projectIds

* selecting a project in the filter menu adds the item to selectedProjectsFullPath

* selecting a project filters the results

* add search icon

* add search icon

* remove searchQuery from scoping results query

* clean-up return statement in fetchGitLabProjects

* remove alias

* make selectedProjectIds nullable

* add search to issue args

* include search string when adding new gitlab issue

* refactor search query to a gql object

* remove useLoadNextOnScrollBottom and increase default projects first

* merge with master

* fix selectedProjects type err

* add search string to differentiate project menu query and include ids in project connection so we can add issues with a filter

* use react-swipeable-views workaround

* add _xGitLabProject resolver

* merge with master

* remove resolverTypes and gitlabTypes

* map over tabs instead of contents

* implement new scope search ui in gitlab

* refactor gitlabSearchQuery from selectedProjectsIds to selectedProjects

* selected projects showing up in current filters

* truncate current filters

* add new scope search to jira

* show jira project names

* improve current filters positioning

* remove gitlab types

* implement new scope search ui in parabol integration

* implement new scope search bar ui in github

* change filter var to status

* query projects from GitLabIntegration and remove fullPath from gitlab search query

* refactor scoping results query to usePagination and add alias to new issue query so it is not affected by parent query filtering

* refactor PokerEstimateHeaderCard to make PokerEstimateHeaderCardContent reusable

* adding commit to play by the gh title rules

* track start of search

* track end of search

* track updated poker scope

* fix(gitlab): add proper client-side alias handling (#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

* add viewerMeetingMember check and remove selectedProjectsIds resolver

* track selected gitlab project filter

* track selected all issues

* track cleared gitlab search

* remove refetchable from gitlab scoping results query

* fix ts error

* keep focus in search input after clearing query

* change searchQueryFilters event track options from string to id

* use nullish coalescing instead of logical or

* make selectedProjectsIds null if empty array

* update SegmentEventTrackOptions

* use optional chaining rather than destructuring many vars

* feat(sprint-poker): GitLab issue is visible in Estimate phase (#6355)

* refactor PokerEstimateHeaderCard to make PokerEstimateHeaderCardContent reusable

* adding commit to play by the gh title rules

* use nullish coalescing instead of logical or

* spread headerFields into PokerEstimateHeaderCardContent

* update event titles and include service

* capitalise search in event tracking

Co-authored-by: Matt Krick <[email protected]>
  • Loading branch information
nickoferrall and mattkrick authored Apr 20, 2022
1 parent 16d556d commit 06f9e30
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 22 deletions.
5 changes: 4 additions & 1 deletion packages/client/components/GitHubScopingSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React, {useEffect} from 'react'
import React, {useEffect, useRef} from 'react'
import {commitLocalUpdate, useFragment} from 'react-relay'
import Atmosphere from '~/Atmosphere'
import useAtmosphere from '../hooks/useAtmosphere'
Expand Down Expand Up @@ -88,6 +88,7 @@ const GitHubScopingSearchInput = (props: Props) => {
const {queryString} = githubSearchQuery
const isEmpty = !queryString
const atmosphere = useAtmosphere()
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
setSearch(atmosphere, meetingId, defaultInput)
}, [])
Expand All @@ -98,6 +99,7 @@ const GitHubScopingSearchInput = (props: Props) => {
}
const clearSearch = () => {
setSearch(atmosphere, meetingId, '')
inputRef.current?.focus()
}

return (
Expand All @@ -107,6 +109,7 @@ const GitHubScopingSearchInput = (props: Props) => {
value={queryString}
onChange={onChange}
placeholder={'Search GitHub issues...'}
ref={inputRef}
/>
<ClearSearchIcon isEmpty={isEmpty} onClick={clearSearch}>
close
Expand Down
7 changes: 6 additions & 1 deletion packages/client/components/GitHubScopingSelectAllIssues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ const GitHubScopingSelectAllIssues = (props: Props) => {
)
return issue?.title ?? 'Unknown Story'
})
UpdatePokerScopeMutation(atmosphere, variables, {onError, onCompleted, contents})
UpdatePokerScopeMutation(atmosphere, variables, {
onError,
onCompleted,
contents,
selectedAll: true
})
}
if (issues.length < 2) return null
const title = getSelectAllTitle(issues.length, usedServiceTaskIds.size, 'issue')
Expand Down
8 changes: 8 additions & 0 deletions packages/client/components/GitLabScopingSearchFilterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import graphql from 'babel-plugin-relay/macro'
import React, {useMemo} from 'react'
import {commitLocalUpdate, PreloadedQuery, usePreloadedQuery} from 'react-relay'
import useSearchFilter from '~/hooks/useSearchFilter'
import SendClientSegmentEventMutation from '~/mutations/SendClientSegmentEventMutation'
import getNonNullEdges from '~/utils/getNonNullEdges'
import useAtmosphere from '../hooks/useAtmosphere'
import {MenuProps} from '../hooks/useMenu'
Expand Down Expand Up @@ -102,6 +103,7 @@ const GitLabScopingSearchFilterMenu = (props: Props) => {
const gitlabSearchQuery = meeting?.gitlabSearchQuery
const {selectedProjectsIds} = gitlabSearchQuery!
const atmosphere = useAtmosphere()
const {viewerId} = atmosphere

const {
query: searchQuery,
Expand Down Expand Up @@ -142,6 +144,12 @@ const GitLabScopingSearchFilterMenu = (props: Props) => {
: [...selectedProjectsIds, projectId]
gitlabSearchQuery.setValue(newSelectedProjectsIds, 'selectedProjectsIds')
})
SendClientSegmentEventMutation(atmosphere, 'Selected Poker Scope Project Filter', {
viewerId,
meetingId,
projectId,
service: 'gitlab'
})
}
return (
<MenuItem
Expand Down
22 changes: 20 additions & 2 deletions packages/client/components/GitLabScopingSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import React, {useRef} from 'react'
import {commitLocalUpdate, useFragment} from 'react-relay'
import Atmosphere from '~/Atmosphere'
import SendClientSegmentEventMutation from '~/mutations/SendClientSegmentEventMutation'
import useAtmosphere from '../hooks/useAtmosphere'
import {PALETTE} from '../styles/paletteV3'
import {GitLabScopingSearchInput_meeting$key} from '../__generated__/GitLabScopingSearchInput_meeting.graphql'
Expand Down Expand Up @@ -64,13 +65,29 @@ const GitLabScopingSearchInput = (props: Props) => {
const {queryString} = gitlabSearchQuery
const isEmpty = !queryString
const atmosphere = useAtmosphere()
const inputRef = useRef<HTMLInputElement>(null)
const {viewerId} = atmosphere

const trackEvent = (eventTitle: string) => {
SendClientSegmentEventMutation(atmosphere, eventTitle, {
viewerId,
meetingId,
service: 'gitlab'
})
}

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {value} = e.target
if (!queryString) {
trackEvent('Started Poker Scope Search')
}
setSearch(atmosphere, meetingId, value)
}

const clearSearch = () => {
trackEvent('Cleared Poker Scope Search')
setSearch(atmosphere, meetingId, '')
inputRef.current?.focus()
}

return (
Expand All @@ -79,7 +96,8 @@ const GitLabScopingSearchInput = (props: Props) => {
autoFocus
value={queryString}
onChange={onChange}
placeholder={'Search GitLab issues...'}
placeholder='Search GitLab issues...'
ref={inputRef}
/>
<ClearSearchIcon isEmpty={isEmpty} onClick={clearSearch}>
close
Expand Down
9 changes: 7 additions & 2 deletions packages/client/components/GitLabScopingSelectAllIssues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import useUnusedRecords from '~/hooks/useUnusedRecords'
import useAtmosphere from '../hooks/useAtmosphere'
import useMutationProps from '../hooks/useMutationProps'
import UpdatePokerScopeMutation from '../mutations/UpdatePokerScopeMutation'
import GitLabIssueId from '../shared/gqlIds/GitLabIssueId'
import {PALETTE} from '../styles/paletteV3'
import {Threshold} from '../types/constEnums'
import getSelectAllTitle from '../utils/getSelectAllTitle'
import {GitLabScopingSelectAllIssues_issues$key} from '../__generated__/GitLabScopingSelectAllIssues_issues.graphql'
import Checkbox from './Checkbox'
import GitLabIssueId from '../shared/gqlIds/GitLabIssueId'

const Item = styled('div')({
display: 'flex',
Expand Down Expand Up @@ -83,7 +83,12 @@ const GitLabScopingSelectAllIssues = (props: Props) => {
)
return issue?.title ?? 'Unknown Story'
})
UpdatePokerScopeMutation(atmosphere, variables, {onError, onCompleted, contents})
UpdatePokerScopeMutation(atmosphere, variables, {
onError,
onCompleted,
contents,
selectedAll: true
})
}
if (issues.length < 2) return null
const title = getSelectAllTitle(issues.length, usedServiceTaskIds.size, 'issue')
Expand Down
11 changes: 9 additions & 2 deletions packages/client/components/JiraScopingSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import React, {useRef} from 'react'
import {commitLocalUpdate, createFragmentContainer} from 'react-relay'
import Atmosphere from '~/Atmosphere'
import useAtmosphere from '../hooks/useAtmosphere'
Expand Down Expand Up @@ -53,17 +53,24 @@ const JiraScopingSearchInput = (props: Props) => {
const {isJQL, queryString} = jiraSearchQuery
const isEmpty = !queryString
const atmosphere = useAtmosphere()
const inputRef = useRef<HTMLInputElement>(null)
const placeholder = isJQL ? `SPRINT = fun AND PROJECT = dev` : 'Search issues on Jira'
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {value} = e.target
setSearch(atmosphere, meetingId, value)
}
const clearSearch = () => {
setSearch(atmosphere, meetingId, '')
inputRef.current?.focus()
}
return (
<Wrapper>
<SearchInput value={queryString} placeholder={placeholder} onChange={onChange} />
<SearchInput
value={queryString}
placeholder={placeholder}
onChange={onChange}
ref={inputRef}
/>
<ClearSearchIcon isEmpty={isEmpty} onClick={clearSearch}>
close
</ClearSearchIcon>
Expand Down
7 changes: 6 additions & 1 deletion packages/client/components/JiraScopingSelectAllIssues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ const JiraScopingSelectAllIssues = (props: Props) => {
const issue = issues.find((issueEdge) => issueEdge.node.id === update.serviceTaskId)
return issue?.node.summary ?? 'Unknown Story'
})
UpdatePokerScopeMutation(atmosphere, variables, {onError, onCompleted, contents})
UpdatePokerScopeMutation(atmosphere, variables, {
onError,
onCompleted,
contents,
selectedAll: true
})
}
if (issues.length < 2) return null
const title = getSelectAllTitle(issues.length, usedServiceTaskIds.size, 'issue')
Expand Down
15 changes: 12 additions & 3 deletions packages/client/components/ParabolScopingSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import React, {useRef} from 'react'
import {commitLocalUpdate, createFragmentContainer} from 'react-relay'
import {PALETTE} from '~/styles/paletteV3'
import Atmosphere from '../Atmosphere'
Expand Down Expand Up @@ -53,13 +53,22 @@ const ParabolScopingSearchInput = (props: Props) => {
const {queryString} = parabolSearchQuery
const isEmpty = !queryString
const atmosphere = useAtmosphere()
const inputRef = useRef<HTMLInputElement>(null)
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(atmosphere, meetingId, e.target.value)
}
const clearSearch = () => setSearch(atmosphere, meetingId, '')
const clearSearch = () => {
setSearch(atmosphere, meetingId, '')
inputRef.current?.focus()
}
return (
<Wrapper>
<SearchInput value={queryString!} placeholder={'Search Parabol tasks'} onChange={onChange} />
<SearchInput
value={queryString!}
placeholder={'Search Parabol tasks'}
onChange={onChange}
ref={inputRef}
/>
<ClearSearchIcon isEmpty={isEmpty} onClick={clearSearch}>
close
</ClearSearchIcon>
Expand Down
7 changes: 6 additions & 1 deletion packages/client/components/ParabolScopingSelectAllTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ const ParabolScopingSelectAllTasks = (props: Props) => {
const task = tasks.find((taskEdge) => taskEdge.node.id === update.serviceTaskId)
return task?.node.plaintextContent ?? 'Unknown Story'
})
UpdatePokerScopeMutation(atmosphere, variables, {onError, onCompleted, contents})
UpdatePokerScopeMutation(atmosphere, variables, {
onError,
onCompleted,
contents,
selectedAll: true
})
}
if (tasks.length < 2) return null
const title = getSelectAllTitle(tasks.length, usedServiceTaskIds.size, 'task')
Expand Down
55 changes: 48 additions & 7 deletions packages/client/mutations/UpdatePokerScopeMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {PALETTE} from '../styles/paletteV3'
import {BaseLocalHandlers, StandardMutation} from '../types/relayMutations'
import convertToTaskContent from '../utils/draftjs/convertToTaskContent'
import splitDraftContent from '../utils/draftjs/splitDraftContent'
import getSearchQueryFromMeeting from '../utils/getSearchQueryFromMeeting'
import clientTempId from '../utils/relay/clientTempId'
import createProxyRecord from '../utils/relay/createProxyRecord'
import {
UpdatePokerScopeMutation as TUpdatePokerScopeMutation,
UpdatePokerScopeMutationResponse
} from '../__generated__/UpdatePokerScopeMutation.graphql'
import SendClientSegmentEventMutation from './SendClientSegmentEventMutation'

graphql`
fragment UpdatePokerScopeMutation_meeting on UpdatePokerScopeSuccess {
Expand Down Expand Up @@ -53,6 +55,20 @@ graphql`
}
}
meeting {
gitlabSearchQuery {
queryString
selectedProjectsIds
}
githubSearchQuery {
queryString
}
jiraSearchQuery {
queryString
projectKeyFilters
}
parabolSearchQuery {
queryString
}
phases {
... on EstimatePhase {
stages {
Expand Down Expand Up @@ -80,16 +96,19 @@ const mutation = graphql`
}
`

type Meeting = NonNullable<UpdatePokerScopeMutationResponse['updatePokerScope']['meeting']>
export type PokerScopeMeeting = NonNullable<
UpdatePokerScopeMutationResponse['updatePokerScope']['meeting']
>

interface Handlers extends BaseLocalHandlers {
contents: string[]
selectedAll?: boolean
}

const UpdatePokerScopeMutation: StandardMutation<TUpdatePokerScopeMutation, Handlers> = (
atmosphere,
variables,
{onError, onCompleted, contents}
{onError, onCompleted, contents, selectedAll}
) => {
return commitMutation<TUpdatePokerScopeMutation>(atmosphere, {
mutation,
Expand All @@ -110,7 +129,7 @@ const UpdatePokerScopeMutation: StandardMutation<TUpdatePokerScopeMutation, Hand
if (!viewer) return
const viewerId = viewer?.getValue('id')
const {meetingId, updates} = variables
const meeting = store.get<Meeting>(meetingId)
const meeting = store.get<PokerScopeMeeting>(meetingId)
if (!meeting) return
const teamId = (meeting.getValue('teamId') || '') as string
const team = store.get(teamId)
Expand Down Expand Up @@ -197,9 +216,8 @@ const UpdatePokerScopeMutation: StandardMutation<TUpdatePokerScopeMutation, Hand
optimisticTask.setLinkedRecord(optimisticTaskIntegration, 'integration')
} else if (service === 'github') {
const bodyHTML = stateToHTML(contentState)
const {issueNumber, nameWithOwner, repoName, repoOwner} = GitHubIssueId.split(
serviceTaskId
)
const {issueNumber, nameWithOwner, repoName, repoOwner} =
GitHubIssueId.split(serviceTaskId)
const repository = createProxyRecord(store, '_xGitHubRepository', {
nameWithOwner,
name: repoName,
Expand Down Expand Up @@ -260,7 +278,30 @@ const UpdatePokerScopeMutation: StandardMutation<TUpdatePokerScopeMutation, Hand
}
})
},
onCompleted,
onCompleted: (res, errors) => {
if (onCompleted) {
onCompleted(res, errors)
}
const {updatePokerScope} = res
const {meeting} = updatePokerScope
if (!meeting) return
const {viewerId} = atmosphere
const {meetingId, updates} = variables
const update = updates[0]!
const {service, action} = update
const searchQuery = getSearchQueryFromMeeting(meeting, service)
if (!searchQuery) return
const [searchQueryString, searchQueryFilters] = searchQuery
SendClientSegmentEventMutation(atmosphere, 'Updated Poker Scope', {
viewerId,
meetingId,
service,
action,
searchQueryString,
searchQueryFilters,
selectedAll
})
},
onError,
cacheConfig: {
metadata: {
Expand Down
24 changes: 24 additions & 0 deletions packages/client/utils/getSearchQueryFromMeeting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {PokerScopeMeeting} from '~/mutations/UpdatePokerScopeMutation'
import {TaskServiceEnum} from '../__generated__/UpdatePokerScopeMutation.graphql'

const getSearchQueryFromMeeting = (meeting: PokerScopeMeeting, service: TaskServiceEnum) => {
switch (service) {
case 'PARABOL':
const {parabolSearchQuery} = meeting
return [parabolSearchQuery.queryString]
case 'github':
const {githubSearchQuery} = meeting
return [githubSearchQuery.queryString]
case 'gitlab':
const {gitlabSearchQuery} = meeting
const {queryString, selectedProjectsIds} = gitlabSearchQuery
return [queryString, selectedProjectsIds]
case 'jira':
const {jiraSearchQuery} = meeting
const {queryString: jiraQueryString, projectKeyFilters} = jiraSearchQuery
return [jiraQueryString, projectKeyFilters]
}
return null
}

export default getSearchQueryFromMeeting
Loading

0 comments on commit 06f9e30

Please sign in to comment.