• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / RepoGovernance / 5821211101

pending completion
5821211101

Pull #510

github

web-flow
Merge e79b78e00 into 3ccf7ed9f
Pull Request #510: Removed configuration link

195 of 294 branches covered (66.33%)

Branch coverage included in aggregate %.

4 of 4 new or added lines in 1 file covered. (100.0%)

593 of 739 relevant lines covered (80.24%)

19.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.92
/src/RepoGovernance.Core/SummaryItemsDA.cs
1
using DotNetCensus.Core.Models;
2
using GitHubActionsDotNet.Models.Dependabot;
3
using GitHubActionsDotNet.Serialization;
4
using RepoAutomation.Core.APIAccess;
5
using RepoAutomation.Core.Helpers;
6
using RepoAutomation.Core.Models;
7
using RepoGovernance.Core.APIAccess;
8
using RepoGovernance.Core.Helpers;
9
using RepoGovernance.Core.Models;
10
using RepoGovernance.Core.TableStorage;
11

12
namespace RepoGovernance.Core
13
{
14
    public static class SummaryItemsDA
15
    {
16
        public static List<UserOwnerRepo> GetRepos(string user)
17
        {
1✔
18
            return DatabaseAccess.GetRepos(user);
1✔
19
        }
1✔
20

21
        /// <summary>
22
        /// Get a list of summary items from Azure Storage
23
        /// </summary>
24
        /// <param name="connectionString"></param>
25
        /// <param name="owner"></param>
26
        /// <returns></returns>
27
        /// <exception cref="ArgumentException"></exception>
28
        public static async Task<List<SummaryItem>> GetSummaryItems(
29
            string? connectionString,
30
            string owner)
31
        {
2✔
32
            List<SummaryItem> results;
33
            if (connectionString != null)
2!
34
            {
2✔
35
                results = await AzureTableStorageDA.GetSummaryItemsFromTable(connectionString, "Summary", owner);
2✔
36
            }
2✔
37
            else
38
            {
×
39
                throw new ArgumentException("connectionstring is null");
×
40
            }
41
            //sort the list
42
            results = results.OrderBy(o => o.Repo).ToList();
66✔
43
            return results;
2✔
44
        }
2✔
45

46
        public static async Task<SummaryItem?> GetSummaryItem(string connectionString,
47
            string user, string owner, string repo)
48
        {
1✔
49
            SummaryItem? result = null;
1✔
50
            List<SummaryItem> summaryItems = await GetSummaryItems(connectionString, user);
1✔
51
            if (summaryItems != null)
1✔
52
            {
1✔
53
                foreach (SummaryItem item in summaryItems)
16✔
54
                {
7✔
55
                    if (item.Owner == owner && item.Repo == repo)
7✔
56
                    {
1✔
57
                        result = item;
1✔
58
                        break;
1✔
59
                    }
60
                }
6✔
61
            }
1✔
62
            return result;
1✔
63
        }
1✔
64

65

66
        /// <summary>
67
        /// Update a single repo's record into Azure Storage
68
        /// </summary>
69
        /// <param name="clientId"></param>
70
        /// <param name="secret"></param>
71
        /// <param name="connectionString"></param>
72
        /// <param name="owner"></param>
73
        /// <param name="repo"></param>
74
        /// <returns></returns>
75
        /// <exception cref="ArgumentException"></exception>
76
        public static async Task<int> UpdateSummaryItem(string? clientId,
77
            string? secret,
78
            string? connectionString,
79
            string? devOpsServiceURL,
80
            string user,
81
            string owner,
82
            string repo,
83
            string? azureTenantId,
84
            string? azureClientId,
85
            string? azureClientSecret,
86
            AzureDeployment? azureDeployment = null)
87
        {
5✔
88
            int itemsUpdated = 0;
5✔
89
            //Initialize the summary item
90
            SummaryItem? summaryItem = new(user, owner, repo);
5✔
91

92
            if (azureDeployment == null && connectionString != null)
5✔
93
            {
1✔
94
                //check to make sure there isn't data in the table already for Azure deployments, and pull it out to save it
95
                SummaryItem? existingItem = await GetSummaryItem(connectionString, user, owner, repo);
1✔
96
                if (existingItem != null && existingItem.AzureDeployment != null)
1!
97
                {
1✔
98
                    azureDeployment = existingItem.AzureDeployment;
1✔
99
                }
1✔
100
            }
1✔
101

102
            try
103
            {
5✔
104
                //Get repo settings
105
                RepoAutomation.Core.Models.Repo? repoSettings = await GitHubApiAccess.GetRepo(clientId, secret, owner, repo);
5✔
106
                if (repoSettings != null)
5✔
107
                {
5✔
108
                    summaryItem.RepoSettings = repoSettings;
5✔
109
                    if (summaryItem.RepoSettings.allow_auto_merge == false)
5!
110
                    {
×
111
                        summaryItem.RepoSettingsRecommendations.Add("Consider enabling 'Allow Auto-Merge' in repo settings to streamline PR merging");
×
112
                    }
×
113
                    if (summaryItem.RepoSettings.delete_branch_on_merge == false)
5!
114
                    {
×
115
                        summaryItem.RepoSettingsRecommendations.Add("Consider disabling 'Delete branch on merge' in repo settings to streamline PR merging and auto-cleanup completed branches");
×
116
                    }
×
117
                    if (summaryItem.RepoSettings.allow_rebase_merge == true)
5!
118
                    {
×
119
                        summaryItem.RepoSettingsRecommendations.Add("Consider disabling 'Allow rebase merge' in repo settings, as rebasing can be confusing");
×
120
                    }
×
121
                }
5✔
122

123
                //Get any actions
124
                List<string>? actions = await GitHubFiles.GetFiles(clientId, secret, owner, repo,
5✔
125
                null, null, ".github/workflows");
5✔
126
                if (actions != null)
5✔
127
                {
5✔
128
                    summaryItem.Actions = actions;
5✔
129
                }
5✔
130
                if (summaryItem.Actions.Count == 0)
5!
131
                {
×
132
                    summaryItem.ActionRecommendations.Add("Consider adding an action to build your project");
×
133
                }
×
134

135
                //Get any dependabot files
136
                List<string>? dependabot = await GitHubFiles.GetFiles(
5✔
137
                    clientId, secret,
5✔
138
                    owner, repo,
5✔
139
                    "dependabot.yml", null, ".github");
5✔
140
                if (dependabot != null)
5✔
141
                {
5✔
142
                    summaryItem.Dependabot = dependabot;
5✔
143
                }
5✔
144
                if (summaryItem.Dependabot.Count >= 1)
5!
145
                {
5✔
146
                    if (summaryItem.Dependabot.Count > 1)
5!
147
                    {
×
148
                        summaryItem.DependabotRecommendations.Add("Consider consolidating your Dependabot files to just one file");
×
149
                    }
×
150
                    summaryItem.DependabotFile = await GitHubFiles.GetFileContents(clientId, secret, owner, repo, ".github/dependabot.yml");
5✔
151
                    summaryItem.DependabotRoot = DependabotSerialization.Deserialize(summaryItem?.DependabotFile?.content);
5!
152
                    if (summaryItem?.DependabotRoot?.updates.Count == 0)
5!
153
                    {
×
154
                        summaryItem.DependabotRecommendations.Add("Dependabot file exists, but is not configured to scan any manifest files");
×
155
                    }
×
156
                }
5✔
157
                else
158
                {
×
159
                    summaryItem.DependabotRecommendations.Add("Consider adding a Dependabot file to automatically update dependencies");
×
160
                }
×
161
                //Check each line of the dependabot file
162
                int actionsCount = 0;
5✔
163
                if (summaryItem?.DependabotRoot?.updates != null)
5!
164
                {
5✔
165
                    foreach (Package? item in summaryItem.DependabotRoot.updates)
59✔
166
                    {
22✔
167
                        if (item.package_ecosystem == "github-actions")
22✔
168
                        {
5✔
169
                            actionsCount++;
5✔
170
                        }
5✔
171
                        if (item.assignees == null || item.assignees.Count == 0)
22!
172
                        {
×
173
                            summaryItem.DependabotRecommendations.Add("Consider adding an assignee to ensure the Dependabot PR has an owner to the " + item.directory + " project, " + item.package_ecosystem + " ecosystem");
×
174
                        }
×
175
                        if (item.open_pull_requests_limit == null)
22✔
176
                        {
2✔
177
                            summaryItem.DependabotRecommendations.Add("Consider adding an open_pull_requests_limit to ensure Dependabot doesn't open too many PR's in the " + item.directory + " project, " + item.package_ecosystem + " ecosystem");
2✔
178
                        }
2✔
179
                    }
22✔
180
                    if (summaryItem.Actions.Count > 0 && actionsCount == 0)
5!
181
                    {
×
182
                        summaryItem.DependabotRecommendations.Add("Consider adding github-actions ecosystem to Dependabot to auto-update actions dependencies");
×
183
                    }
×
184
                }
5✔
185

186
                //Get branch policies
187
                BranchProtectionPolicy? branchPolicies = await GitHubApiAccess.GetBranchProtectionPolicy(clientId, secret, owner, repo, "main");
5✔
188
                if (branchPolicies == null)
5!
189
                {
×
190
                    summaryItem?.BranchPoliciesRecommendations.Add("Consider adding a branch policy to protect the main branch");
×
191
                }
×
192
                else if (summaryItem != null)
5✔
193
                {
5✔
194
                    summaryItem.BranchPolicies = branchPolicies;
5✔
195
                    if (summaryItem.BranchPolicies.enforce_admins == null || summaryItem.BranchPolicies.enforce_admins.enabled == false)
5!
196
                    {
1✔
197
                        summaryItem.BranchPoliciesRecommendations.Add("Consider enabling 'Enforce Admins', to ensure that all users of the repo must follow branch policy rules");
1✔
198
                    }
1✔
199
                    if (summaryItem.BranchPolicies.required_conversation_resolution == null || summaryItem.BranchPolicies.required_conversation_resolution.enabled == false)
5!
200
                    {
3✔
201
                        summaryItem.BranchPoliciesRecommendations.Add("Consider enabling 'Require Conversation Resolution', to ensure that all comments have been resolved in the PR before merging to the main branch");
3✔
202
                    }
3✔
203
                    if (summaryItem?.BranchPolicies?.required_status_checks?.checks == null || summaryItem.BranchPolicies.required_status_checks.checks.Length == 0)
5!
204
                    {
×
205
                        summaryItem?.BranchPoliciesRecommendations.Add("Consider adding status checks to the branch policy, to ensure that builds and tests pass successfully before the branch is merged to the main branch");
×
206
                    }
×
207
                }
5✔
208

209
                //Get Gitversion files
210
                List<string>? gitversion = await GitHubFiles.GetFiles(
5✔
211
                    clientId, secret,
5✔
212
                    owner, repo,
5✔
213
                    "GitVersion.yml", null, "");
5✔
214
                if (summaryItem != null && gitversion != null && gitversion.Count > 0)
5!
215
                {
5✔
216
                    summaryItem.GitVersion = gitversion;
5✔
217
                }
5✔
218
                else
219
                {
×
220
                    summaryItem?.GitVersionRecommendations.Add("Consider adding Git Versioning to this repo");
×
221
                }
×
222

223
                //Get Frameworks, using the DotNetCensus library we built in another project
224
                DotNetCensus.Core.Models.Repo? repo2 = new(owner, repo)
5✔
225
                {
5✔
226
                    User = clientId,
5✔
227
                    Password = secret,
5✔
228
                    Branch = "main"
5✔
229
                };
5✔
230
                List<FrameworkSummary> frameworkSummaries = DotNetCensus.Core.Main.GetFrameworkSummary(null, repo2, false);
5✔
231
                foreach (FrameworkSummary frameworkSummary in frameworkSummaries)
31✔
232
                {
8✔
233
                    Framework framework = new()
8✔
234
                    {
8✔
235
                        Name = frameworkSummary.Framework,
8✔
236
                        Color = DotNetRepoScanner.GetColorFromStatus(frameworkSummary.Status),
8✔
237
                        Count = frameworkSummary.Count
8✔
238
                    };
8✔
239
                    if (frameworkSummary.Framework != null &&
8!
240
                        summaryItem?.DotNetFrameworks.Where(p => p.Name == frameworkSummary.Framework).FirstOrDefault() == null)
12✔
241
                    {
8✔
242
                        summaryItem?.DotNetFrameworks.Add(framework);
8!
243
                    }
8✔
244
                }
8✔
245
                //Order the frameworks so they appear in alphabetically
246
                if (summaryItem != null && summaryItem.DotNetFrameworks != null)
5!
247
                {
5✔
248
                    summaryItem.DotNetFrameworks = summaryItem.DotNetFrameworks.OrderBy(o => o.Name).ToList();
13✔
249
                }
5✔
250

251
                //Get the last commit
252
                string? lastCommitSha = await GitHubApiAccess.GetLastCommit(clientId, secret, owner, repo);
5✔
253
                if (summaryItem != null && lastCommitSha != null)
5!
254
                {
5✔
255
                    summaryItem.LastCommitSha = lastCommitSha;
5✔
256
                }
5✔
257

258
                //Get Pull Request details
259
                List<PullRequest> pullRequests = await GitHubApiAccess.GetPullRequests(clientId, secret, owner, repo);
5✔
260
                if (summaryItem != null && pullRequests != null && pullRequests.Count > 0)
5!
261
                {
4✔
262
                    foreach (PullRequest pr in pullRequests)
62✔
263
                    {
25✔
264
                        //Check if the PR has been reviewed
265
                        if (pr.Number != null)
25✔
266
                        {
25✔
267
                            List<PRReview> prReviews = await GitHubApiAccess.GetPullRequestReview(clientId, secret, owner, repo, pr.Number);
25✔
268
                            if (prReviews != null && prReviews.Count > 0 && prReviews[^1] != null)
25✔
269
                            {
24✔
270
                                string? state = prReviews[^1].state;
24✔
271
                                if (state == "APPROVED")
24✔
272
                                {
24✔
273
                                    pr.Approved = true;
24✔
274
                                }
24✔
275
                            }
24✔
276
                        }
25✔
277
                        //Check if the PR has was created by dependabot
278
                        foreach (string prLabel in pr.Labels)
167✔
279
                        {
46✔
280
                            if (prLabel == "dependencies")
46✔
281
                            {
23✔
282
                                pr.IsDependabotPR = true;
23✔
283
                            }
23✔
284
                        }
46✔
285
                    }
25✔
286
                    summaryItem.PullRequests = pullRequests;
4✔
287
                }
4✔
288

289
                //Get DevOps Metrics
290
                if (summaryItem != null && devOpsServiceURL != null)
5!
291
                {
5✔
292
                    DevOpsMetricServiceApi devopsAPI = new(devOpsServiceURL);
5✔
293
                    DORASummaryItem? dORASummaryItem = await devopsAPI.GetDORASummaryItems(owner, repo);
5✔
294
                    if (dORASummaryItem != null)
5✔
295
                    {
5✔
296
                        summaryItem.DORASummary = dORASummaryItem;
5✔
297
                    }
5✔
298
                }
5✔
299

300
                //Get the Release info
301
                Release? release = await GitHubApiAccess.GetReleaseLatest(clientId, secret, owner, repo);
5✔
302
                if (summaryItem != null && release != null)
5!
303
                {
5✔
304
                    summaryItem.Release = release;
5✔
305
                }
5✔
306

307
                //Get Coveralls.io Code Coverage
308
                CoverallsCodeCoverage? coverallsCodeCoverage = await CoverallsCodeCoverageApi.GetCoverallsCodeCoverage(owner, repo);
5✔
309
                if (summaryItem != null && coverallsCodeCoverage != null)
5!
310
                {
5✔
311
                    summaryItem.CoverallsCodeCoverage = coverallsCodeCoverage;
5✔
312
                }
5✔
313

314
                //Get SonarCloud metrics (note that we use user here instead of owner)
315
                SonarCloud? sonarCloud = await SonarCloudApi.GetSonarCloudMetrics(user, repo);
5✔
316
                if (summaryItem != null && sonarCloud != null)
5!
317
                {
5✔
318
                    summaryItem.SonarCloud = sonarCloud;
5✔
319
                }
5✔
320

321
                //Get Repo Language stats
322
                List<RepoLanguage>? repoLanguages = await RepoLanguageHelper.GetRepoLanguages(clientId, secret, owner, repo);
5✔
323
                if (summaryItem != null && repoLanguages != null && repoLanguages.Count > 0)
5!
324
                {
5✔
325
                    summaryItem.RepoLanguages = repoLanguages;
5✔
326
                    summaryItem.RepoLanguagesLastUpdated = DateTime.Now;
5✔
327
                }
5✔
328
                else if (summaryItem != null && repoLanguages == null && connectionString != null)
×
329
                {
×
330
                    //If we couldn't find languages last time - lets pull up the existing summary item and use that
331
                    SummaryItem? summaryItem2 = await GetSummaryItem(connectionString, user, owner, repo);
×
332
                    if (summaryItem2 != null && summaryItem2.RepoLanguages != null)
×
333
                    {
×
334
                        summaryItem.RepoLanguages = summaryItem2.RepoLanguages;
×
335
                        if (summaryItem2.RepoLanguagesLastUpdated != null)
×
336
                        {
×
337
                            summaryItem.RepoLanguagesLastUpdated = summaryItem2.RepoLanguagesLastUpdated;
×
338
                        }
×
339
                    }
×
340
                }
×
341

342
                //If there are azure deployment records, then process the summary item and save the detail
343
                if (azureDeployment != null &&
5!
344
                    azureTenantId != null &&
5✔
345
                    azureClientId != null &&
5✔
346
                    azureClientSecret != null)
5✔
347
                {
5✔
348
                    AzureApi azureApi = new(azureTenantId, azureClientId, azureClientSecret);
5✔
349
                    azureDeployment = await azureApi.GetApplications(azureDeployment);
5✔
350
                    if (summaryItem != null && azureDeployment != null)
5!
351
                    {
5✔
352
                        summaryItem.AzureDeployment = azureDeployment;
5✔
353
                    }
5✔
354
                }
5✔
355

356
            }
5✔
357
            catch (Exception ex)
×
358
            {
×
359
                //If there was an error, update with the error message
360
                string message = ex.ToString();
×
361
                if (connectionString != null)
×
362
                {
×
363
                    summaryItem = await GetSummaryItem(connectionString, user, owner, repo);
×
364
                    if (summaryItem != null)
×
365
                    {
×
366
                        summaryItem.LastUpdatedMessage = "Error at " + DateTime.Now.ToString() + ": " + ex.ToString();
×
367
                    }
×
368
                }
×
369
            }
×
370

371
            //Save the summary item
372
            if (connectionString != null && summaryItem != null)
5!
373
            {
5✔
374
                itemsUpdated += await AzureTableStorageDA.UpdateSummaryItemsIntoTable(connectionString, user, owner, repo, summaryItem);
5✔
375
            }
5✔
376
            else
377
            {
×
378
                throw new ArgumentException("connectionstring is null");
×
379
            }
380

381
            return itemsUpdated;
5✔
382
        }
5✔
383

384
        //TODO: convert this bool to int to count updates!
385
        public static async Task<bool> ApproveSummaryItemPRs(string? clientId,
386
            string? secret,
387
            //string? connectionString,
388
            //string? devOpsServiceURL,
389
            //string user,
390
            string owner,
391
            string repo,
392
            string approver)
393
        {
×
394
            //int itemsUpdated = 0;
395

396
            bool result = await GitHubApiAccess.ApprovePullRequests(clientId, secret, owner, repo, approver);
×
397

398
            return result;
×
399
            ;
400
        }
×
401
    }
402
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc