Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit c748a60

Browse files
authored
Merge pull request #1944 from github/fixes/327-default-url-tab-AB-testing
Show clone URL tab by default (A/B testing)
2 parents b952af5 + ffc52b8 commit c748a60

File tree

5 files changed

+116
-5
lines changed

5 files changed

+116
-5
lines changed

src/GitHub.App/Services/RepositoryCloneService.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,20 @@ public async Task CloneRepository(
121121
try
122122
{
123123
await vsGitServices.Clone(cloneUrl, repositoryPath, true, progress);
124+
124125
await usageTracker.IncrementCounter(x => x.NumberOfClones);
126+
127+
var repositoryUrl = new UriString(cloneUrl).ToRepositoryUrl();
128+
var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl);
129+
if (isDotCom)
130+
{
131+
await usageTracker.IncrementCounter(x => x.NumberOfGitHubClones);
132+
}
133+
else
134+
{
135+
// If it isn't a GitHub URL, assume it's an Enterprise URL
136+
await usageTracker.IncrementCounter(x => x.NumberOfEnterpriseClones);
137+
}
125138
}
126139
catch (Exception ex)
127140
{

src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class RepositoryCloneViewModel : ViewModelBase, IRepositoryCloneViewModel
2323
readonly IOperatingSystem os;
2424
readonly IConnectionManager connectionManager;
2525
readonly IRepositoryCloneService service;
26+
readonly IUsageService usageService;
27+
readonly IUsageTracker usageTracker;
2628
readonly IReadOnlyList<IRepositoryCloneTabViewModel> tabs;
2729
string path;
2830
IRepositoryModel previousRepository;
@@ -34,13 +36,17 @@ public RepositoryCloneViewModel(
3436
IOperatingSystem os,
3537
IConnectionManager connectionManager,
3638
IRepositoryCloneService service,
39+
IUsageService usageService,
40+
IUsageTracker usageTracker,
3741
IRepositorySelectViewModel gitHubTab,
3842
IRepositorySelectViewModel enterpriseTab,
3943
IRepositoryUrlViewModel urlTab)
4044
{
4145
this.os = os;
4246
this.connectionManager = connectionManager;
4347
this.service = service;
48+
this.usageService = usageService;
49+
this.usageTracker = usageTracker;
4450

4551
GitHubTab = gitHubTab;
4652
EnterpriseTab = enterpriseTab;
@@ -119,6 +125,33 @@ public async Task InitializeAsync(IConnection connection)
119125
}
120126

121127
this.WhenAnyValue(x => x.SelectedTabIndex).Subscribe(x => tabs[x].Activate().Forget());
128+
129+
// Users in group A will see the URL tab by default
130+
if (await IsGroupA().ConfigureAwait(false))
131+
{
132+
SelectedTabIndex = 2;
133+
}
134+
135+
switch (SelectedTabIndex)
136+
{
137+
case 0:
138+
usageTracker.IncrementCounter(model => model.NumberOfCloneViewGitHubTab).Forget();
139+
break;
140+
case 1:
141+
usageTracker.IncrementCounter(model => model.NumberOfCloneViewEnterpriseTab).Forget();
142+
break;
143+
case 2:
144+
usageTracker.IncrementCounter(model => model.NumberOfCloneViewUrlTab).Forget();
145+
break;
146+
}
147+
}
148+
149+
// Put 50% of users in group A
150+
async Task<bool> IsGroupA()
151+
{
152+
var userGuid = await usageService.GetUserGuid().ConfigureAwait(false);
153+
var lastByte = userGuid.ToByteArray().Last();
154+
return lastByte % 2 == 0;
122155
}
123156

124157
void BrowseForDirectory()

src/GitHub.Exports/Models/UsageModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public class MeasuresModel
8686
public int ExecuteToggleInlineCommentMarginCommand { get; set; }
8787
public int NumberOfPullRequestFileMarginToggleInlineCommentMargin { get; set; }
8888
public int NumberOfPullRequestFileMarginViewChanges { get; set; }
89+
public int NumberOfCloneViewGitHubTab { get; set; }
90+
public int NumberOfCloneViewEnterpriseTab { get; set; }
91+
public int NumberOfCloneViewUrlTab { get; set; }
92+
public int NumberOfGitHubClones { get; set; }
93+
public int NumberOfEnterpriseClones { get; set; }
8994
}
9095
}
9196
}

test/GitHub.App.UnitTests/Services/RepositoryCloneServiceTests.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,13 @@ public async Task ClonesToRepositoryPathAsync()
2727
await vsGitServices.Received().Clone("https://github.com/foo/bar", @"c:\dev\bar", true);
2828
}
2929

30-
[Test]
31-
public async Task UpdatesMetricsWhenRepositoryClonedAsync()
30+
[TestCase("https://github.com/foo/bar", 1, nameof(UsageModel.MeasuresModel.NumberOfClones))]
31+
[TestCase("https://github.com/foo/bar", 1, nameof(UsageModel.MeasuresModel.NumberOfGitHubClones))]
32+
[TestCase("https://github.com/foo/bar", 0, nameof(UsageModel.MeasuresModel.NumberOfEnterpriseClones))]
33+
[TestCase("https://enterprise.com/foo/bar", 1, nameof(UsageModel.MeasuresModel.NumberOfClones))]
34+
[TestCase("https://enterprise.com/foo/bar", 1, nameof(UsageModel.MeasuresModel.NumberOfEnterpriseClones))]
35+
[TestCase("https://enterprise.com/foo/bar", 0, nameof(UsageModel.MeasuresModel.NumberOfGitHubClones))]
36+
public async Task UpdatesMetricsWhenRepositoryClonedAsync(string cloneUrl, int numberOfCalls, string counterName)
3237
{
3338
var serviceProvider = Substitutes.ServiceProvider;
3439
var operatingSystem = serviceProvider.GetOperatingSystem();
@@ -37,12 +42,12 @@ public async Task UpdatesMetricsWhenRepositoryClonedAsync()
3742
var usageTracker = Substitute.For<IUsageTracker>();
3843
var cloneService = new RepositoryCloneService(operatingSystem, vsGitServices, graphqlFactory, usageTracker);
3944

40-
await cloneService.CloneRepository("https://github.com/foo/bar", @"c:\dev\bar");
45+
await cloneService.CloneRepository(cloneUrl, @"c:\dev\bar");
4146
var model = UsageModel.Create(Guid.NewGuid());
4247

43-
await usageTracker.Received().IncrementCounter(
48+
await usageTracker.Received(numberOfCalls).IncrementCounter(
4449
Arg.Is<Expression<Func<UsageModel.MeasuresModel, int>>>(x =>
45-
((MemberExpression)x.Body).Member.Name == nameof(model.Measures.NumberOfClones)));
50+
((MemberExpression)x.Body).Member.Name == counterName));
4651
}
4752
}
4853
}

test/GitHub.App.UnitTests/ViewModels/Dialog/Clone/RepositoryCloneViewModelTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
22
using System.ComponentModel;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Numerics;
36
using System.Threading.Tasks;
47
using GitHub.Extensions;
58
using GitHub.Models;
@@ -26,6 +29,42 @@ public async Task GitHubPage_Is_Initialized()
2629
target.EnterpriseTab.DidNotReceiveWithAnyArgs().Initialize(null);
2730
}
2831

32+
[TestCase("https://github.com", false, 0)]
33+
[TestCase("https://enterprise.com", false, 1)]
34+
[TestCase("https://github.com", true, 2, Description = "Show URL tab for GitHub connections")]
35+
[TestCase("https://enterprise.com", true, 2, Description = "Show URL tab for Enterprise connections")]
36+
public async Task Default_SelectedTabIndex_For_Group(string address, bool isGroupA, int expectTabIndex)
37+
{
38+
var cm = CreateConnectionManager(address);
39+
var connection = cm.Connections[0];
40+
var usageService = CreateUsageService(isGroupA);
41+
var target = CreateTarget(connectionManager: cm, usageService: usageService);
42+
43+
await target.InitializeAsync(connection);
44+
45+
Assert.That(target.SelectedTabIndex, Is.EqualTo(expectTabIndex));
46+
}
47+
48+
[TestCase("https://github.com", false, 1, nameof(UsageModel.MeasuresModel.NumberOfCloneViewGitHubTab))]
49+
[TestCase("https://enterprise.com", false, 1, nameof(UsageModel.MeasuresModel.NumberOfCloneViewEnterpriseTab))]
50+
[TestCase("https://github.com", true, 1, nameof(UsageModel.MeasuresModel.NumberOfCloneViewUrlTab))]
51+
[TestCase("https://enterprise.com", true, 1, nameof(UsageModel.MeasuresModel.NumberOfCloneViewUrlTab))]
52+
public async Task IncrementCounter_Showing_Default_Tab(string address, bool isGroupA, int numberOfCalls, string counterName)
53+
{
54+
var cm = CreateConnectionManager(address);
55+
var connection = cm.Connections[0];
56+
var usageService = CreateUsageService(isGroupA);
57+
var usageTracker = Substitute.For<IUsageTracker>();
58+
var target = CreateTarget(connectionManager: cm, usageService: usageService, usageTracker: usageTracker);
59+
usageTracker.IncrementCounter(null).ReturnsForAnyArgs(Task.CompletedTask);
60+
61+
await target.InitializeAsync(connection).ConfigureAwait(false);
62+
63+
await usageTracker.Received(numberOfCalls).IncrementCounter(
64+
Arg.Is<Expression<Func<UsageModel.MeasuresModel, int>>>(x =>
65+
((MemberExpression)x.Body).Member.Name == counterName));
66+
}
67+
2968
[Test]
3069
public async Task EnterprisePage_Is_Initialized()
3170
{
@@ -274,6 +313,8 @@ static RepositoryCloneViewModel CreateTarget(
274313
IOperatingSystem os = null,
275314
IConnectionManager connectionManager = null,
276315
IRepositoryCloneService service = null,
316+
IUsageService usageService = null,
317+
IUsageTracker usageTracker = null,
277318
IRepositorySelectViewModel gitHubTab = null,
278319
IRepositorySelectViewModel enterpriseTab = null,
279320
IRepositoryUrlViewModel urlTab = null,
@@ -282,6 +323,8 @@ static RepositoryCloneViewModel CreateTarget(
282323
os = os ?? Substitute.For<IOperatingSystem>();
283324
connectionManager = connectionManager ?? CreateConnectionManager("https://github.com");
284325
service = service ?? CreateRepositoryCloneService(defaultClonePath);
326+
usageService = usageService ?? CreateUsageService();
327+
usageTracker = usageTracker ?? Substitute.For<IUsageTracker>();
285328
gitHubTab = gitHubTab ?? CreateSelectViewModel();
286329
enterpriseTab = enterpriseTab ?? CreateSelectViewModel();
287330
urlTab = urlTab ?? Substitute.For<IRepositoryUrlViewModel>();
@@ -290,11 +333,23 @@ static RepositoryCloneViewModel CreateTarget(
290333
os,
291334
connectionManager,
292335
service,
336+
usageService,
337+
usageTracker,
293338
gitHubTab,
294339
enterpriseTab,
295340
urlTab);
296341
}
297342

343+
static IUsageService CreateUsageService(bool isGroupA = false)
344+
{
345+
var usageService = Substitute.For<IUsageService>();
346+
var guidBytes = new byte[16];
347+
guidBytes[guidBytes.Length - 1] = (byte)(isGroupA ? 0 : 1);
348+
var userGuid = new Guid(guidBytes);
349+
usageService.GetUserGuid().Returns(userGuid);
350+
return usageService;
351+
}
352+
298353
static IRepositoryModel CreateRepositoryModel(string repo = "owner/repo")
299354
{
300355
var split = repo.Split('/');

0 commit comments

Comments
 (0)