refactor: add segment tracking for git sync (#4841)

* add segment tracking for git sync

* add suggestion

* fix lint

* add other cases for tracking

* add type fix
This commit is contained in:
Mark Kim 2022-06-07 14:10:47 -04:00 committed by GitHub
parent 5b1ab1b69d
commit c2a0abca62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 72 additions and 38 deletions

View File

@ -2,6 +2,8 @@ import { database as db } from '../common/database';
import type { GitCredentials } from '../sync/git/git-vcs';
import type { BaseModel } from './index';
export type OauthProviderName = 'gitlab' | 'github' | 'custom';
export type GitRepository = BaseModel & BaseGitRepository;
export const name = 'Git Repository';

View File

@ -1,5 +1,6 @@
import { AuthCallback, AuthFailureCallback, AuthSuccessCallback, GitAuth, MessageCallback } from 'isomorphic-git';
import { OauthProviderName } from '../../models/git-repository';
import type { GitCredentials } from './git-vcs';
import { getAccessToken as getGitHubAccessToken } from './github-oauth-provider';
import { getAccessToken as getGitlabAccessToken, refreshToken as refreshGitlabToken } from './gitlab-oauth-provider';
@ -95,6 +96,14 @@ const onAuth = (credentials?: GitCredentials): AuthCallback => (): GitAuth => {
};
};
export const getOauth2FormatName = (credentials?: GitCredentials | null): OauthProviderName | undefined => {
if (credentials && 'oauth2format' in credentials) {
return credentials.oauth2format;
}
return;
};
export const gitCallbacks = (credentials?: GitCredentials | null) => ({
onMessage,
onAuthFailure: onAuthFailure(credentials ?? undefined),

View File

@ -14,6 +14,7 @@ import type { GitRepository } from '../../../models/git-repository';
import type { Workspace } from '../../../models/workspace';
import type { GitLogEntry, GitVCS } from '../../../sync/git/git-vcs';
import { MemClient } from '../../../sync/git/mem-client';
import { getOauth2FormatName } from '../../../sync/git/utils';
import { initialize as initializeEntities } from '../../redux/modules/entities';
import type {
SetupGitRepositoryCallback,
@ -126,10 +127,10 @@ class GitSyncDropdown extends PureComponent<Props, State> {
}
const bufferId = await db.bufferChanges();
const providerName = getOauth2FormatName(gitRepository.credentials);
try {
await vcs.pull(gitRepository.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'pull'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'pull'), providerName });
} catch (err) {
showError({
title: 'Error Pulling Repository',
@ -184,10 +185,10 @@ class GitSyncDropdown extends PureComponent<Props, State> {
}
const bufferId = await db.bufferChanges();
const providerName = getOauth2FormatName(gitRepository.credentials);
try {
await vcs.push(gitRepository.credentials, force);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', force ? 'force_push' : 'push'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', force ? 'force_push' : 'push'), providerName });
} catch (err) {
if (err.code === 'PushRejectedError') {
this._dropdown?.hide();
@ -205,7 +206,7 @@ class GitSyncDropdown extends PureComponent<Props, State> {
title: 'Error Pushing Repository',
error: err,
});
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', force ? 'force_push' : 'push', err.message));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', force ? 'force_push' : 'push', err.message), providerName });
}
}
@ -235,8 +236,10 @@ class GitSyncDropdown extends PureComponent<Props, State> {
}
async _handleCommit() {
const { gitRepository } = this.props;
showModal(GitStagingModal, {
onCommit: this._refreshState,
gitRepository,
});
}

View File

@ -7,6 +7,7 @@ import { AUTOBIND_CFG } from '../../../common/constants';
import { database as db } from '../../../common/database';
import type { GitRepository } from '../../../models/git-repository';
import { GitVCS } from '../../../sync/git/git-vcs';
import { getOauth2FormatName } from '../../../sync/git/utils';
import { initialize as initializeEntities } from '../../redux/modules/entities';
import { Modal } from '../base/modal';
import { ModalBody } from '../base/modal-body';
@ -111,10 +112,11 @@ export class GitBranchesModal extends PureComponent<Props, State> {
async _handleCreate(e: React.SyntheticEvent<HTMLFormElement>) {
e.preventDefault();
await this._errorHandler(async () => {
const { vcs } = this.props;
const { vcs, gitRepository } = this.props;
const { newBranchName } = this.state;
const providerName = getOauth2FormatName(gitRepository.credentials);
await vcs.checkout(newBranchName);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'create_branch'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'create_branch'), providerName });
await this._refreshState({
newBranchName: '',
});
@ -123,19 +125,21 @@ export class GitBranchesModal extends PureComponent<Props, State> {
async _handleMerge(branch: string) {
await this._errorHandler(async () => {
const { vcs } = this.props;
const { vcs, gitRepository } = this.props;
const providerName = getOauth2FormatName(gitRepository.credentials);
await vcs.merge(branch);
// Apparently merge doesn't update the working dir so need to checkout too
await this._handleCheckout(branch);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'merge_branch'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'merge_branch'), providerName });
});
}
async _handleDelete(branch: string) {
await this._errorHandler(async () => {
const { vcs } = this.props;
const { vcs, gitRepository } = this.props;
const providerName = getOauth2FormatName(gitRepository.credentials);
await vcs.deleteBranch(branch);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'delete_branch'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'delete_branch'), providerName });
await this._refreshState();
});
}
@ -151,10 +155,11 @@ export class GitBranchesModal extends PureComponent<Props, State> {
async _handleCheckout(branch: string) {
await this._errorHandler(async () => {
const { vcs, handleInitializeEntities } = this.props;
const { vcs, gitRepository, handleInitializeEntities } = this.props;
const bufferId = await db.bufferChanges();
const providerName = getOauth2FormatName(gitRepository.credentials);
await vcs.checkout(branch);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'checkout_branch'));
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'checkout_branch'), providerName });
await db.flushChanges(bufferId, true);
await handleInitializeEntities();
await this._refreshState();

View File

@ -4,7 +4,7 @@ import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { AUTOBIND_CFG } from '../../../../common/constants';
import { docsGitSync } from '../../../../common/documentation';
import type { GitRepository } from '../../../../models/git-repository';
import type { GitRepository, OauthProviderName } from '../../../../models/git-repository';
import { deleteGitRepository } from '../../../../models/helpers/git-repository-operations';
import { Link } from '../../base/link';
import { Modal } from '../../base/modal';
@ -88,7 +88,7 @@ interface Props {
onReset: () => void;
}
const oauth2Formats = ['github', 'gitlab', 'custom'];
const oauth2Formats: OauthProviderName[] = ['github', 'gitlab', 'custom'];
const ModalForm = (props: Props) => {
const { gitRepository, onSubmit, onReset } = props;
@ -102,7 +102,7 @@ const ModalForm = (props: Props) => {
const initialTab = !gitRepository ? 'github' : oauth2format;
const [selectedTab, setTab] = useState(initialTab);
const [selectedTab, setTab] = useState<OauthProviderName>(initialTab);
const selectedTabIndex = oauth2Formats.indexOf(selectedTab);
return (

View File

@ -10,10 +10,12 @@ import { database as db } from '../../../common/database';
import { strings } from '../../../common/strings';
import * as models from '../../../models';
import { isApiSpec } from '../../../models/api-spec';
import { GitRepository } from '../../../models/git-repository';
import type { Workspace } from '../../../models/workspace';
import { gitRollback } from '../../../sync/git/git-rollback';
import { GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME, GitVCS } from '../../../sync/git/git-vcs';
import parseGitPath from '../../../sync/git/parse-git-path';
import { getOauth2FormatName } from '../../../sync/git/utils';
import { IndeterminateCheckbox } from '../base/indeterminate-checkbox';
import { Modal } from '../base/modal';
import { ModalBody } from '../base/modal-body';
@ -34,6 +36,7 @@ interface Item {
interface Props {
workspace: Workspace;
vcs: GitVCS;
gitRepository: GitRepository | null;
}
interface State {
@ -78,7 +81,7 @@ export class GitStagingModal extends PureComponent<Props, State> {
}
async _handleCommit() {
const { vcs } = this.props;
const { vcs, gitRepository } = this.props;
const { items, message } = this.state;
// Set the stage
@ -97,7 +100,8 @@ export class GitStagingModal extends PureComponent<Props, State> {
}
await vcs.commit(message);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'commit'));
const providerName = getOauth2FormatName(gitRepository?.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'commit'), providerName });
this.modal?.hide();
if (typeof this.onCommit === 'function') {
@ -122,7 +126,8 @@ export class GitStagingModal extends PureComponent<Props, State> {
newItems[p].staged = doStage || forceAdd;
}
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', doStage ? 'stage_all' : 'unstage_all'));
const providerName = getOauth2FormatName(this.props.gitRepository?.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', doStage ? 'stage_all' : 'unstage_all'), providerName });
this.setState({
items: newItems,
});
@ -137,7 +142,9 @@ export class GitStagingModal extends PureComponent<Props, State> {
}
newItems[gitPath].staged = !newItems[gitPath].staged;
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', newItems[gitPath].staged ? 'stage' : 'unstage'));
const providerName = getOauth2FormatName(this.props.gitRepository?.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', newItems[gitPath].staged ? 'stage' : 'unstage'), providerName });
this.setState({
items: newItems,
});
@ -312,12 +319,16 @@ export class GitStagingModal extends PureComponent<Props, State> {
async _handleRollbackSingle(item: Item) {
await this._handleRollback([item]);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'rollback'));
const providerName = getOauth2FormatName(this.props.gitRepository?.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'rollback'), providerName });
}
async _handleRollbackAll(items: Item[]) {
await this._handleRollback(items);
trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'rollback_all'));
const providerName = getOauth2FormatName(this.props.gitRepository?.credentials);
trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'rollback_all'), providerName });
}
renderItem(item: Item) {

View File

@ -585,7 +585,7 @@ export class Wrapper extends PureComponent<WrapperProps, State> {
{activeWorkspace && gitVCS ? (
<Fragment>
<GitStagingModal ref={registerModal} workspace={activeWorkspace} vcs={gitVCS} />
<GitStagingModal ref={registerModal} workspace={activeWorkspace} vcs={gitVCS} gitRepository={activeGitRepository} />
<GitLogModal ref={registerModal} vcs={gitVCS} />
{activeGitRepository !== null && (
<GitBranchesModal

View File

@ -14,7 +14,7 @@ import { isWorkspace, Workspace, WorkspaceScopeKeys } from '../../../models/work
import { forceWorkspaceScopeToDesign } from '../../../sync/git/force-workspace-scope-to-design';
import { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME } from '../../../sync/git/git-vcs';
import { shallowClone } from '../../../sync/git/shallow-clone';
import { addDotGit, translateSSHtoHTTP } from '../../../sync/git/utils';
import { addDotGit, getOauth2FormatName, translateSSHtoHTTP } from '../../../sync/git/utils';
import { showAlert, showError, showModal } from '../../components/modals';
import { GitRepositorySettingsModal } from '../../components/modals/git-repository-settings-modal';
import { selectActiveProject } from '../selectors';
@ -28,13 +28,14 @@ export type UpdateGitRepositoryCallback = (arg0: { gitRepository: GitRepository
* Update git repository settings
* */
export const updateGitRepository: UpdateGitRepositoryCallback = ({ gitRepository }) => {
const providerName = getOauth2FormatName(gitRepository.credentials);
return () => {
trackSegmentEvent(SegmentEvent.vcsSyncStart, vcsSegmentEventProperties('git', 'update'));
trackSegmentEvent(SegmentEvent.vcsSyncStart, { ...vcsSegmentEventProperties('git', 'update'), providerName });
showModal(GitRepositorySettingsModal, {
gitRepository,
onSubmitEdits: async gitRepoPatch => {
await models.gitRepository.update(gitRepository, gitRepoPatch);
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'update'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'update'), providerName });
},
});
};
@ -52,7 +53,9 @@ export const setupGitRepository: SetupGitRepositoryCallback = ({ createFsClient,
trackSegmentEvent(SegmentEvent.vcsSyncStart, vcsSegmentEventProperties('git', 'setup'));
showModal(GitRepositorySettingsModal, {
gitRepository: null,
onSubmitEdits: async gitRepoPatch => {
onSubmitEdits: async (gitRepoPatch: GitRepository) => {
const providerName = getOauth2FormatName(gitRepoPatch.credentials);
dispatch(loadStart());
try {
@ -70,7 +73,7 @@ export const setupGitRepository: SetupGitRepositoryCallback = ({ createFsClient,
message: err.message,
error: err,
});
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'setup', err.message));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'setup', err.message), providerName });
return;
}
@ -85,15 +88,15 @@ export const setupGitRepository: SetupGitRepositoryCallback = ({ createFsClient,
message:
'This repository is already connected to Insomnia; try creating a clone from the dashboard instead.',
});
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'setup', 'existing insomnia data'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'setup', 'existing insomnia data'), providerName });
return;
}
}
await createGitRepository(workspace._id, gitRepoPatch);
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'setup'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'setup'), providerName });
} catch (err) {
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'setup', err.message));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'setup', err.message), providerName });
} finally {
dispatch(loadStop());
}
@ -166,6 +169,7 @@ export const cloneGitRepository = ({ createFsClient }: {
repoSettingsPatch.uri = translateSSHtoHTTP(repoSettingsPatch.uri);
let fsClient = createFsClient();
const providerName = getOauth2FormatName(repoSettingsPatch.credentials);
try {
await shallowClone({
fsClient,
@ -178,7 +182,7 @@ export const cloneGitRepository = ({ createFsClient }: {
message: originalUriError.message,
});
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', originalUriError.message));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', originalUriError.message), providerName });
return;
}
@ -198,7 +202,7 @@ export const cloneGitRepository = ({ createFsClient }: {
message: `Failed to clone with original url (${repoSettingsPatch.uri}): ${originalUriError.message};\n\nAlso failed to clone with \`.git\` suffix added (${dotGitUri}): ${dotGitError.message}`,
});
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', dotGitError.message));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', dotGitError.message), providerName });
return;
}
}
@ -207,7 +211,7 @@ export const cloneGitRepository = ({ createFsClient }: {
if (!(await containsInsomniaWorkspaceDir(fsClient))) {
dispatch(noDocumentFound(repoSettingsPatch));
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', 'no directory found'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', 'no directory found'), providerName });
return;
}
@ -217,14 +221,14 @@ export const cloneGitRepository = ({ createFsClient }: {
if (workspaces.length === 0) {
dispatch(noDocumentFound(repoSettingsPatch));
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', 'no workspaces found'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', 'no workspaces found'), providerName });
return;
}
if (workspaces.length > 1) {
cloneProblem('Multiple workspaces found in repository; expected one.');
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', 'multiple workspaces found'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', 'multiple workspaces found'), providerName });
return;
}
@ -243,7 +247,7 @@ export const cloneGitRepository = ({ createFsClient }: {
</>,
);
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone', 'workspace already exists'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', 'workspace already exists'), providerName });
return;
}
@ -286,7 +290,7 @@ export const cloneGitRepository = ({ createFsClient }: {
// Flush DB changes
await db.flushChanges(bufferId);
dispatch(loadStop());
trackSegmentEvent(SegmentEvent.vcsSyncComplete, vcsSegmentEventProperties('git', 'clone'));
trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone'), providerName });
},
});
},