diff --git a/packages/client/components/GitHubScopingSearchInput.tsx b/packages/client/components/GitHubScopingSearchInput.tsx index 594a90d748b..a5d14ef83df 100644 --- a/packages/client/components/GitHubScopingSearchInput.tsx +++ b/packages/client/components/GitHubScopingSearchInput.tsx @@ -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' @@ -88,6 +88,7 @@ const GitHubScopingSearchInput = (props: Props) => { const {queryString} = githubSearchQuery const isEmpty = !queryString const atmosphere = useAtmosphere() + const inputRef = useRef(null) useEffect(() => { setSearch(atmosphere, meetingId, defaultInput) }, []) @@ -98,6 +99,7 @@ const GitHubScopingSearchInput = (props: Props) => { } const clearSearch = () => { setSearch(atmosphere, meetingId, '') + inputRef.current?.focus() } return ( @@ -107,6 +109,7 @@ const GitHubScopingSearchInput = (props: Props) => { value={queryString} onChange={onChange} placeholder={'Search GitHub issues...'} + ref={inputRef} /> close diff --git a/packages/client/components/GitHubScopingSelectAllIssues.tsx b/packages/client/components/GitHubScopingSelectAllIssues.tsx index 589e943fda0..7430ba523f1 100644 --- a/packages/client/components/GitHubScopingSelectAllIssues.tsx +++ b/packages/client/components/GitHubScopingSelectAllIssues.tsx @@ -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') diff --git a/packages/client/components/GitLabScopingSearchFilterMenu.tsx b/packages/client/components/GitLabScopingSearchFilterMenu.tsx index 98d8fed38c9..dc481692123 100644 --- a/packages/client/components/GitLabScopingSearchFilterMenu.tsx +++ b/packages/client/components/GitLabScopingSearchFilterMenu.tsx @@ -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' @@ -102,6 +103,7 @@ const GitLabScopingSearchFilterMenu = (props: Props) => { const gitlabSearchQuery = meeting?.gitlabSearchQuery const {selectedProjectsIds} = gitlabSearchQuery! const atmosphere = useAtmosphere() + const {viewerId} = atmosphere const { query: searchQuery, @@ -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 ( { const {queryString} = gitlabSearchQuery const isEmpty = !queryString const atmosphere = useAtmosphere() + const inputRef = useRef(null) + const {viewerId} = atmosphere + + const trackEvent = (eventTitle: string) => { + SendClientSegmentEventMutation(atmosphere, eventTitle, { + viewerId, + meetingId, + service: 'gitlab' + }) + } const onChange = (e: React.ChangeEvent) => { 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 ( @@ -79,7 +96,8 @@ const GitLabScopingSearchInput = (props: Props) => { autoFocus value={queryString} onChange={onChange} - placeholder={'Search GitLab issues...'} + placeholder='Search GitLab issues...' + ref={inputRef} /> close diff --git a/packages/client/components/GitLabScopingSelectAllIssues.tsx b/packages/client/components/GitLabScopingSelectAllIssues.tsx index 67a82ebb60f..27271bf7fc8 100644 --- a/packages/client/components/GitLabScopingSelectAllIssues.tsx +++ b/packages/client/components/GitLabScopingSelectAllIssues.tsx @@ -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', @@ -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') diff --git a/packages/client/components/JiraScopingSearchInput.tsx b/packages/client/components/JiraScopingSearchInput.tsx index 2a0db3b74cb..c949b3834a1 100644 --- a/packages/client/components/JiraScopingSearchInput.tsx +++ b/packages/client/components/JiraScopingSearchInput.tsx @@ -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' @@ -53,6 +53,7 @@ const JiraScopingSearchInput = (props: Props) => { const {isJQL, queryString} = jiraSearchQuery const isEmpty = !queryString const atmosphere = useAtmosphere() + const inputRef = useRef(null) const placeholder = isJQL ? `SPRINT = fun AND PROJECT = dev` : 'Search issues on Jira' const onChange = (e: React.ChangeEvent) => { const {value} = e.target @@ -60,10 +61,16 @@ const JiraScopingSearchInput = (props: Props) => { } const clearSearch = () => { setSearch(atmosphere, meetingId, '') + inputRef.current?.focus() } return ( - + close diff --git a/packages/client/components/JiraScopingSelectAllIssues.tsx b/packages/client/components/JiraScopingSelectAllIssues.tsx index b22d5158690..d351db44f38 100644 --- a/packages/client/components/JiraScopingSelectAllIssues.tsx +++ b/packages/client/components/JiraScopingSelectAllIssues.tsx @@ -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') diff --git a/packages/client/components/ParabolScopingSearchInput.tsx b/packages/client/components/ParabolScopingSearchInput.tsx index 8dfbd844bca..509793293d9 100644 --- a/packages/client/components/ParabolScopingSearchInput.tsx +++ b/packages/client/components/ParabolScopingSearchInput.tsx @@ -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' @@ -53,13 +53,22 @@ const ParabolScopingSearchInput = (props: Props) => { const {queryString} = parabolSearchQuery const isEmpty = !queryString const atmosphere = useAtmosphere() + const inputRef = useRef(null) const onChange = (e: React.ChangeEvent) => { setSearch(atmosphere, meetingId, e.target.value) } - const clearSearch = () => setSearch(atmosphere, meetingId, '') + const clearSearch = () => { + setSearch(atmosphere, meetingId, '') + inputRef.current?.focus() + } return ( - + close diff --git a/packages/client/components/ParabolScopingSelectAllTasks.tsx b/packages/client/components/ParabolScopingSelectAllTasks.tsx index c41d8f0d786..93e126e57b3 100644 --- a/packages/client/components/ParabolScopingSelectAllTasks.tsx +++ b/packages/client/components/ParabolScopingSelectAllTasks.tsx @@ -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') diff --git a/packages/client/mutations/UpdatePokerScopeMutation.ts b/packages/client/mutations/UpdatePokerScopeMutation.ts index e23a9e9a3b8..8733e40de7a 100644 --- a/packages/client/mutations/UpdatePokerScopeMutation.ts +++ b/packages/client/mutations/UpdatePokerScopeMutation.ts @@ -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 { @@ -53,6 +55,20 @@ graphql` } } meeting { + gitlabSearchQuery { + queryString + selectedProjectsIds + } + githubSearchQuery { + queryString + } + jiraSearchQuery { + queryString + projectKeyFilters + } + parabolSearchQuery { + queryString + } phases { ... on EstimatePhase { stages { @@ -80,16 +96,19 @@ const mutation = graphql` } ` -type Meeting = NonNullable +export type PokerScopeMeeting = NonNullable< + UpdatePokerScopeMutationResponse['updatePokerScope']['meeting'] +> interface Handlers extends BaseLocalHandlers { contents: string[] + selectedAll?: boolean } const UpdatePokerScopeMutation: StandardMutation = ( atmosphere, variables, - {onError, onCompleted, contents} + {onError, onCompleted, contents, selectedAll} ) => { return commitMutation(atmosphere, { mutation, @@ -110,7 +129,7 @@ const UpdatePokerScopeMutation: StandardMutation(meetingId) + const meeting = store.get(meetingId) if (!meeting) return const teamId = (meeting.getValue('teamId') || '') as string const team = store.get(teamId) @@ -197,9 +216,8 @@ const UpdatePokerScopeMutation: StandardMutation { + 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: { diff --git a/packages/client/utils/getSearchQueryFromMeeting.ts b/packages/client/utils/getSearchQueryFromMeeting.ts new file mode 100644 index 00000000000..5e2cc392f58 --- /dev/null +++ b/packages/client/utils/getSearchQueryFromMeeting.ts @@ -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 diff --git a/packages/server/graphql/types/SegmentEventTrackOptions.ts b/packages/server/graphql/types/SegmentEventTrackOptions.ts index c54d5023c5c..14dd9b079df 100644 --- a/packages/server/graphql/types/SegmentEventTrackOptions.ts +++ b/packages/server/graphql/types/SegmentEventTrackOptions.ts @@ -1,5 +1,13 @@ -import {GraphQLID, GraphQLInputObjectType, GraphQLInt, GraphQLString} from 'graphql' +import { + GraphQLBoolean, + GraphQLID, + GraphQLInputObjectType, + GraphQLInt, + GraphQLList, + GraphQLString +} from 'graphql' import NewMeetingPhaseTypeEnum from './NewMeetingPhaseTypeEnum' +import TaskServiceEnum from './TaskServiceEnum' const SegmentEventTrackOptions = new GraphQLInputObjectType({ name: 'SegmentEventTrackOptions', @@ -13,7 +21,13 @@ const SegmentEventTrackOptions = new GraphQLInputObjectType({ meetingId: {type: GraphQLID}, reflectionId: {type: GraphQLID}, viewerId: {type: GraphQLID}, - reflectionsCount: {type: GraphQLInt} + reflectionsCount: {type: GraphQLInt}, + action: {type: GraphQLString}, + searchQueryString: {type: GraphQLString}, + service: {type: TaskServiceEnum}, + searchQueryFilters: {type: GraphQLList(GraphQLID)}, + projectId: {type: GraphQLID}, + selectedAll: {type: GraphQLBoolean} }) })