From b6e7b4b06c67039e45db10a3c792acdc6bc5834b Mon Sep 17 00:00:00 2001 From: Aleksandr Krasilnikov Date: Wed, 12 Jun 2024 11:32:22 +0200 Subject: [PATCH] IJPL-156647: merge: add statistics GitOrigin-RevId: 8bb96df414312d166b070e9e90c874eef0b8c746 --- .../diff/merge/MergeStatisticsAggregator.kt | 36 +++++++++++++++ .../diff/merge/MergeThreesideViewer.java | 33 +++++++++++++- .../statistics/MergeStatisticsCollector.kt | 45 +++++++++++++++++++ .../src/META-INF/PlatformExtensions.xml | 1 + 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 platform/diff-impl/src/com/intellij/diff/merge/MergeStatisticsAggregator.kt create mode 100644 platform/diff-impl/src/com/intellij/diff/statistics/MergeStatisticsCollector.kt diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeStatisticsAggregator.kt b/platform/diff-impl/src/com/intellij/diff/merge/MergeStatisticsAggregator.kt new file mode 100644 index 000000000000..67740ec09b5b --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/merge/MergeStatisticsAggregator.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.diff.merge + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +internal class MergeStatisticsAggregator( + val changes: Int, + val autoResolvable: Int, + val conflicts: Int, +) { + var unresolved: Int = -1 + val initialTimestamp: Long = System.currentTimeMillis() + + private val resolvedByAiChanges = mutableSetOf() + private val rolledBackAfterAI = mutableSetOf() + private val undoneAfterAi = mutableSetOf() + + fun resolvedByAi(): Int = resolvedByAiChanges.size + fun rolledBackAfterAI(): Int = rolledBackAfterAI.size + fun undoneAfterAI(): Int = undoneAfterAi.size + + fun wasResolvedByAi(index: Int) { + resolvedByAiChanges.add(index) + } + + fun wasRolledBackAfterAI(index: Int) { + resolvedByAiChanges.remove(index) + rolledBackAfterAI.add(index) + } + + fun wasUndoneAfterAI(index: Int) { + resolvedByAiChanges.remove(index) + undoneAfterAi.add(index) + } +} \ No newline at end of file diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeThreesideViewer.java b/platform/diff-impl/src/com/intellij/diff/merge/MergeThreesideViewer.java index e6272187f9bb..c096c82b23fb 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/MergeThreesideViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/merge/MergeThreesideViewer.java @@ -17,6 +17,7 @@ import com.intellij.diff.contents.DocumentContent; import com.intellij.diff.fragments.MergeLineFragment; import com.intellij.diff.requests.ContentDiffRequest; import com.intellij.diff.requests.SimpleDiffRequest; +import com.intellij.diff.statistics.MergeStatisticsCollector; import com.intellij.diff.tools.holders.EditorHolderFactory; import com.intellij.diff.tools.holders.TextEditorHolder; import com.intellij.diff.tools.simple.ThreesideTextDiffViewerEx; @@ -118,6 +119,7 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { private final Action myLeftResolveAction; private final Action myRightResolveAction; protected final Action myAcceptResolveAction; + private MergeStatisticsAggregator myAggregator; @NotNull protected final MergeContext myMergeContext; @NotNull protected final TextMergeRequest myMergeRequest; @@ -298,10 +300,17 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { } protected void doFinishMerge(@NotNull final MergeResult result) { + if (result == MergeResult.RESOLVED) { + logMergeFinished(MergeResultSource.DIALOG_BUTTON); + } destroyChangedBlocks(); myMergeContext.finishMerge(result); } + public enum MergeResultSource { + DIALOG_BUTTON, + NOTIFICATION + } // // Diff // @@ -516,6 +525,15 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { myCurrentIgnorePolicy = ignorePolicy; myResolveImportConflicts = getTextSettings().isAutoResolveImportConflicts(); + // build initial statistics + int autoResolvableChanges = ContainerUtil.count(getAllChanges(), c -> canResolveChangeAutomatically(c, ThreeSide.BASE)); + + myAggregator = new MergeStatisticsAggregator( + getAllChanges().size(), + autoResolvableChanges, + getConflictsCount() + ); + if (myResolveImportConflicts) { List importChanges = ContainerUtil.filter(getChanges(), change -> change.isImportChange()); if (importChanges.size() != fragmentsWithMetadata.getFragments().size()) { @@ -729,6 +747,7 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { @NlsSafe String message = XmlStringUtil.wrapInHtmlTag(DiffBundle.message("merge.all.changes.processed.message.text"), "a"); DiffBalloons.showSuccessPopup(title, message, point, this, () -> { if (isDisposed() || myLoadingPanel.isLoading()) return; + logMergeFinished(MergeResultSource.NOTIFICATION); destroyChangedBlocks(); myMergeContext.finishMerge(MergeResult.RESOLVED); }); @@ -825,6 +844,7 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { @ApiStatus.Internal @RequiresEdt public void markChangeResolvedWithAI(@NotNull TextMergeChange change) { + myAggregator.wasResolvedByAi(change.getIndex()); change.markChangeResolvedWithAI(); markChangeResolved(change); } @@ -889,7 +909,6 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { @RequiresWriteLock void resetResolvedChange(TextMergeChange change) { if (!change.isResolved()) return; - MergeLineFragment changeFragment = change.getFragment(); int startLine = changeFragment.getStartLine(ThreeSide.BASE); int endLine = changeFragment.getEndLine(ThreeSide.BASE); @@ -900,7 +919,9 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { myModel.replaceChange(change.getIndex(), baseContent); change.resetState(); - + if (change.isResolvedWithAI()) { + myAggregator.wasRolledBackAfterAI(change.getIndex()); + } onChangeResolved(change); myModel.invalidateHighlighters(change.getIndex()); } @@ -979,6 +1000,9 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { TextMergeChange change = myAllMergeChanges.get(state.myIndex); boolean wasResolved = change.isResolved(); + if (change.isResolvedWithAI()) { + myAggregator.wasUndoneAfterAI(change.getIndex()); + } change.restoreState(state); if (wasResolved != change.isResolved()) onChangeResolved(change); } @@ -1278,6 +1302,11 @@ public class MergeThreesideViewer extends ThreesideTextDiffViewerEx { protected abstract boolean isEnabled(@NotNull TextMergeChange change); } + private void logMergeFinished(MergeResultSource source) { + myAggregator.setUnresolved(getChanges().size()); + MergeStatisticsCollector.INSTANCE.logMergeFinished(myProject, source, myAggregator); + } + private class IgnoreSelectedChangesSideAction extends ApplySelectedChangesActionBase { @NotNull private final Side mySide; diff --git a/platform/diff-impl/src/com/intellij/diff/statistics/MergeStatisticsCollector.kt b/platform/diff-impl/src/com/intellij/diff/statistics/MergeStatisticsCollector.kt new file mode 100644 index 000000000000..2b9b27303fe2 --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/statistics/MergeStatisticsCollector.kt @@ -0,0 +1,45 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.diff.statistics + +import com.intellij.diff.merge.MergeStatisticsAggregator +import com.intellij.diff.merge.MergeThreesideViewer.MergeResultSource +import com.intellij.internal.statistic.eventLog.EventLogGroup +import com.intellij.internal.statistic.eventLog.events.EnumEventField +import com.intellij.internal.statistic.eventLog.events.EventFields +import com.intellij.internal.statistic.eventLog.events.IntEventField +import com.intellij.internal.statistic.eventLog.events.VarargEventId +import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector +import com.intellij.openapi.project.Project +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +internal object MergeStatisticsCollector : CounterUsagesCollector() { + private val GROUP: EventLogGroup = EventLogGroup("vcs.merge", 1) + + private val SOURCE: EnumEventField = EventFields.Enum("source", MergeResultSource::class.java) + private val CHANGES: IntEventField = EventFields.Int("changes") + private val AUTO_RESOLVABLE = EventFields.Int("autoResolvable") + private val CONFLICTS = EventFields.Int("conflicts") + private val UNRESOLVED = EventFields.Int("unresolved") + private val AI_RESOLVED = EventFields.Int("aiResolved") + private val AI_ROLLED_BACK = EventFields.Int("rolledBackAfterAi") + private val AI_UNDONE = EventFields.Int("undoneAfterAi") + + private val FILE_MERGED_EVENT: VarargEventId = GROUP.registerVarargEvent("file.merged", CHANGES, EventFields.DurationMs, AUTO_RESOLVABLE, CONFLICTS, UNRESOLVED, AI_RESOLVED, AI_ROLLED_BACK, AI_UNDONE) + + override fun getGroup(): EventLogGroup = GROUP + + fun logMergeFinished(project: Project?, source: MergeResultSource, aggregator: MergeStatisticsAggregator) { + FILE_MERGED_EVENT.log(project) { + add(SOURCE.with(source)) + add(CHANGES.with(aggregator.changes)) + add(EventFields.DurationMs.with(System.currentTimeMillis() - aggregator.initialTimestamp)) + add(AUTO_RESOLVABLE.with(aggregator.autoResolvable)) + add(CONFLICTS.with(aggregator.conflicts)) + add(UNRESOLVED.with(aggregator.unresolved)) + add(AI_RESOLVED.with(aggregator.resolvedByAi())) + add(AI_ROLLED_BACK.with(aggregator.rolledBackAfterAI())) + add(AI_UNDONE.with(aggregator.undoneAfterAI())) + } + } +} diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 777c04031ea9..fb0e94e26888 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -955,6 +955,7 @@ implementation="com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceVendorsListValidator"/> +