diff --git a/server/src/main/java/org/eclipse/openvsx/UserAPI.java b/server/src/main/java/org/eclipse/openvsx/UserAPI.java index c5a47c4e3..8e6be77a8 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/UserAPI.java @@ -12,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.eclipse.openvsx.eclipse.EclipseService; import org.eclipse.openvsx.entities.NamespaceMembership; +import org.eclipse.openvsx.entities.PublisherStatistics; import org.eclipse.openvsx.entities.UserData; import org.eclipse.openvsx.json.*; import org.eclipse.openvsx.repositories.RepositoryService; @@ -366,4 +367,18 @@ public ResponseEntity signPublisherAgreement() { } } + @GetMapping( + path = "/user/statistics", + produces = MediaType.APPLICATION_JSON_VALUE + ) + public List getPublisherStatistics() { + var user = users.findLoggedInUser(); + if (user == null) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN); + } + + return repositories.findPublisherStatisticsByUser(user).stream() + .map(PublisherStatistics::toJson) + .toList(); + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java b/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java index b29949a40..955c2b776 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java +++ b/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java @@ -22,6 +22,7 @@ import org.eclipse.openvsx.migration.HandlerJobRequest; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; +import org.eclipse.openvsx.statistics.MonthlyStatisticsJobRequestHandler; import org.eclipse.openvsx.storage.StorageUtilService; import org.eclipse.openvsx.util.*; import org.jobrunr.scheduling.JobRequestScheduler; @@ -78,7 +79,7 @@ public AdminService( @EventListener public void applicationStarted(ApplicationStartedEvent event) { - var jobRequest = new HandlerJobRequest<>(MonthlyAdminStatisticsJobRequestHandler.class); + var jobRequest = new HandlerJobRequest<>(MonthlyStatisticsJobRequestHandler.class); scheduler.scheduleRecurrently("MonthlyAdminStatistics", Cron.monthly(1, 0, 3), ZoneId.of("UTC"), jobRequest); } diff --git a/server/src/main/java/org/eclipse/openvsx/entities/PublisherStatistics.java b/server/src/main/java/org/eclipse/openvsx/entities/PublisherStatistics.java new file mode 100644 index 000000000..0804b6e3a --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/entities/PublisherStatistics.java @@ -0,0 +1,100 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.entities; + +import jakarta.persistence.*; +import org.eclipse.openvsx.json.PublisherStatisticsJson; + +import java.util.*; + +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "year", "month"})}) +public class PublisherStatistics { + + @Id + @GeneratedValue(generator = "publisherStatisticsSeq") + @SequenceGenerator(name = "publisherStatisticsSeq", sequenceName = "publisher_statistics_seq") + private long id; + + @ManyToOne + @JoinColumn(name = "user_data") + private UserData user; + + private int year; + + private int month; + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "extension_identifier") + @Column(name = "downloads") + private Map extensionDownloads; + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "extension_identifier") + @Column(name = "downloads") + private Map extensionTotalDownloads; + + public PublisherStatisticsJson toJson() { + var json = new PublisherStatisticsJson(); + json.setYear(year); + json.setMonth(month); + json.setExtensionDownloads(extensionDownloads); + json.setExtensionTotalDownloads(extensionTotalDownloads); + return json; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public UserData getUser() { + return user; + } + + public void setUser(UserData user) { + this.user = user; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public int getMonth() { + return month; + } + + public void setMonth(int month) { + this.month = month; + } + + public Map getExtensionDownloads() { + return extensionDownloads; + } + + public void setExtensionDownloads(Map extensionDownloads) { + this.extensionDownloads = extensionDownloads; + } + + public Map getExtensionTotalDownloads() { + return extensionTotalDownloads; + } + + public void setExtensionTotalDownloads(Map extensionTotalDownloads) { + this.extensionTotalDownloads = extensionTotalDownloads; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/json/PublisherStatisticsJson.java b/server/src/main/java/org/eclipse/openvsx/json/PublisherStatisticsJson.java new file mode 100644 index 000000000..fb0181a89 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/json/PublisherStatisticsJson.java @@ -0,0 +1,64 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.json; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PublisherStatisticsJson extends ResultJson { + + public static PublisherStatisticsJson error(String message) { + var result = new PublisherStatisticsJson(); + result.setError(message); + return result; + } + + private int year; + + private int month; + + private Map extensionDownloads; + + private Map extensionTotalDownloads; + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public int getMonth() { + return month; + } + + public void setMonth(int month) { + this.month = month; + } + + public Map getExtensionDownloads() { + return extensionDownloads; + } + + public void setExtensionDownloads(Map extensionDownlaods) { + this.extensionDownloads = extensionDownlaods; + } + + public Map getExtensionTotalDownloads() { + return extensionTotalDownloads; + } + + public void setExtensionTotalDownloads(Map extensionTotalDownlaods) { + this.extensionTotalDownloads = extensionTotalDownlaods; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java index d53db1250..7db6b0d5c 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java @@ -11,6 +11,7 @@ import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.statistics.MembershipDownloadCount; import org.eclipse.openvsx.util.ExtensionId; import org.eclipse.openvsx.web.SitemapRow; import org.jooq.Record; @@ -24,8 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static org.eclipse.openvsx.jooq.Tables.EXTENSION; -import static org.eclipse.openvsx.jooq.Tables.NAMESPACE; +import static org.eclipse.openvsx.jooq.Tables.*; @Component public class ExtensionJooqRepository { @@ -270,4 +270,40 @@ public boolean hasExtension(String namespace, String extension) { .and(EXTENSION.NAME.equalIgnoreCase(extension)) ); } + + public List findMembershipDownloads(int offset, int limit) { + return dsl.select(USER_DATA.ID, NAMESPACE.NAME, EXTENSION.NAME, EXTENSION.DOWNLOAD_COUNT) + .from(USER_DATA) + .join(NAMESPACE_MEMBERSHIP).on(NAMESPACE_MEMBERSHIP.USER_DATA.eq(USER_DATA.ID)) + .join(NAMESPACE).on(NAMESPACE.ID.eq(NAMESPACE_MEMBERSHIP.NAMESPACE)) + .join(EXTENSION).on(EXTENSION.NAMESPACE_ID.eq(NAMESPACE.ID)) + .where(USER_DATA.PROVIDER.eq("github")) + .and(EXTENSION.ACTIVE.eq(true)) + .orderBy(USER_DATA.ID, NAMESPACE.NAME, EXTENSION.NAME) + .offset(offset) + .limit(limit) + .fetch(row -> new MembershipDownloadCount( + row.get(USER_DATA.ID), + row.get(NAMESPACE.NAME), + row.get(EXTENSION.NAME), + row.get(EXTENSION.DOWNLOAD_COUNT)) + ); + } + + public List findMembershipDownloads(String loginName) { + return dsl.select(USER_DATA.ID, NAMESPACE.NAME, EXTENSION.NAME, EXTENSION.DOWNLOAD_COUNT) + .from(USER_DATA) + .join(NAMESPACE_MEMBERSHIP).on(NAMESPACE_MEMBERSHIP.USER_DATA.eq(USER_DATA.ID)) + .join(NAMESPACE).on(NAMESPACE.ID.eq(NAMESPACE_MEMBERSHIP.NAMESPACE)) + .join(EXTENSION).on(EXTENSION.NAMESPACE_ID.eq(NAMESPACE.ID)) + .where(USER_DATA.PROVIDER.eq("github")) + .and(USER_DATA.LOGIN_NAME.eq(loginName)) + .and(EXTENSION.ACTIVE.eq(true)) + .fetch(row -> new MembershipDownloadCount( + row.get(USER_DATA.ID), + row.get(NAMESPACE.NAME), + row.get(EXTENSION.NAME), + row.get(EXTENSION.DOWNLOAD_COUNT)) + ); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/PublisherStatisticsRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/PublisherStatisticsRepository.java new file mode 100644 index 000000000..9416fdac0 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/repositories/PublisherStatisticsRepository.java @@ -0,0 +1,22 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.repositories; + +import org.eclipse.openvsx.entities.PublisherStatistics; +import org.eclipse.openvsx.entities.UserData; +import org.springframework.data.repository.Repository; +import org.springframework.data.util.Streamable; + +public interface PublisherStatisticsRepository extends Repository { + + PublisherStatistics findByYearAndMonthAndUserId(int year, int month, long userId); + + Streamable findByUser(UserData user); +} diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java index 8748a34bf..3bacce0cf 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -12,6 +12,7 @@ import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.QueryRequest; import org.eclipse.openvsx.json.VersionTargetPlatformsJson; +import org.eclipse.openvsx.statistics.MembershipDownloadCount; import org.eclipse.openvsx.util.ExtensionId; import org.eclipse.openvsx.util.NamingUtil; import org.eclipse.openvsx.web.SitemapRow; @@ -58,6 +59,7 @@ public class RepositoryService { private final MigrationItemRepository migrationItemRepo; private final SignatureKeyPairRepository signatureKeyPairRepo; private final SignatureKeyPairJooqRepository signatureKeyPairJooqRepo; + private final PublisherStatisticsRepository publisherStatisticsRepo; public RepositoryService( NamespaceRepository namespaceRepo, @@ -81,7 +83,8 @@ public RepositoryService( AdminStatisticCalculationsRepository adminStatisticCalculationsRepo, MigrationItemRepository migrationItemRepo, SignatureKeyPairRepository signatureKeyPairRepo, - SignatureKeyPairJooqRepository signatureKeyPairJooqRepo + SignatureKeyPairJooqRepository signatureKeyPairJooqRepo, + PublisherStatisticsRepository publisherStatisticsRepo ) { this.namespaceRepo = namespaceRepo; this.namespaceJooqRepo = namespaceJooqRepo; @@ -105,6 +108,7 @@ public RepositoryService( this.migrationItemRepo = migrationItemRepo; this.signatureKeyPairRepo = signatureKeyPairRepo; this.signatureKeyPairJooqRepo = signatureKeyPairJooqRepo; + this.publisherStatisticsRepo = publisherStatisticsRepo; } public Namespace findNamespace(String name) { @@ -667,4 +671,20 @@ public boolean hasExtension(String namespace, String extension) { public Streamable findDeprecatedExtensions(Extension replacement) { return extensionRepo.findByReplacement(replacement); } + + public PublisherStatistics findPublisherStatisticsByYearAndMonthAndUserId(int year, int month, long userId) { + return publisherStatisticsRepo.findByYearAndMonthAndUserId(year, month, userId); + } + + public Streamable findPublisherStatisticsByUser(UserData user) { + return publisherStatisticsRepo.findByUser(user); + } + + public List findMembershipDownloads(int offset, int limit) { + return extensionJooqRepo.findMembershipDownloads(offset, limit); + } + + public List findMembershipDownloads(String loginName) { + return extensionJooqRepo.findMembershipDownloads(loginName); + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java index 7a70f0abc..eb18cd701 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java @@ -13,6 +13,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.Repository; +import org.springframework.data.util.Streamable; public interface UserDataRepository extends Repository { @@ -22,4 +23,5 @@ public interface UserDataRepository extends Repository { long count(); + Streamable findByProvider(String provider); } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/statistics/AdminStatisticsJobRequestHandler.java similarity index 89% rename from server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandler.java rename to server/src/main/java/org/eclipse/openvsx/statistics/AdminStatisticsJobRequestHandler.java index 235623543..c7ad09653 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/statistics/AdminStatisticsJobRequestHandler.java @@ -7,32 +7,28 @@ * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ -package org.eclipse.openvsx.admin; +package org.eclipse.openvsx.statistics; import org.eclipse.openvsx.entities.AdminStatistics; import org.eclipse.openvsx.repositories.RepositoryService; import org.jobrunr.jobs.lambdas.JobRequestHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component -public class AdminStatisticsJobRequestHandler implements JobRequestHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(AdminStatisticsJobRequestHandler.class); +public class AdminStatisticsJobRequestHandler implements JobRequestHandler { private final RepositoryService repositories; - private final AdminStatisticsService service; + private final StatisticsService service; - public AdminStatisticsJobRequestHandler(RepositoryService repositories, AdminStatisticsService service) { + public AdminStatisticsJobRequestHandler(RepositoryService repositories, StatisticsService service) { this.repositories = repositories; this.service = service; } @Override - public void run(AdminStatisticsJobRequest jobRequest) throws Exception { + public void run(StatisticsJobRequest jobRequest) throws Exception { var year = jobRequest.getYear(); var month = jobRequest.getMonth(); diff --git a/server/src/main/java/org/eclipse/openvsx/statistics/MembershipDownloadCount.java b/server/src/main/java/org/eclipse/openvsx/statistics/MembershipDownloadCount.java new file mode 100644 index 000000000..7d6f77862 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/statistics/MembershipDownloadCount.java @@ -0,0 +1,12 @@ +/** ****************************************************************************** + * Copyright (c) 2025 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.statistics; + +public record MembershipDownloadCount(long userId, String namespace, String extension, long downloadCount) { } diff --git a/server/src/main/java/org/eclipse/openvsx/admin/MonthlyAdminStatisticsJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/statistics/MonthlyStatisticsJobRequestHandler.java similarity index 67% rename from server/src/main/java/org/eclipse/openvsx/admin/MonthlyAdminStatisticsJobRequestHandler.java rename to server/src/main/java/org/eclipse/openvsx/statistics/MonthlyStatisticsJobRequestHandler.java index 81fe88f69..799b795e9 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/MonthlyAdminStatisticsJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/statistics/MonthlyStatisticsJobRequestHandler.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ -package org.eclipse.openvsx.admin; +package org.eclipse.openvsx.statistics; import org.eclipse.openvsx.migration.HandlerJobRequest; import org.eclipse.openvsx.util.TimeUtil; @@ -19,11 +19,11 @@ import java.util.UUID; @Component -public class MonthlyAdminStatisticsJobRequestHandler implements JobRequestHandler> { +public class MonthlyStatisticsJobRequestHandler implements JobRequestHandler> { private final JobRequestScheduler scheduler; - public MonthlyAdminStatisticsJobRequestHandler(JobRequestScheduler scheduler) { + public MonthlyStatisticsJobRequestHandler(JobRequestScheduler scheduler) { this.scheduler = scheduler; } @@ -35,6 +35,10 @@ public void run(HandlerJobRequest jobRequest) throws Exception { var jobIdText = "AdminStatistics::year=" + year + ",month=" + month; var jobId = UUID.nameUUIDFromBytes(jobIdText.getBytes(StandardCharsets.UTF_8)); - scheduler.enqueue(jobId, new AdminStatisticsJobRequest(year, month)); + scheduler.enqueue(jobId, new StatisticsJobRequest<>(AdminStatisticsJobRequestHandler.class, year, month)); + + jobIdText = "PublisherStatistics::year=" + year + ",month=" + month; + jobId = UUID.nameUUIDFromBytes(jobIdText.getBytes(StandardCharsets.UTF_8)); + scheduler.enqueue(jobId, new StatisticsJobRequest<>(PublisherStatisticsJobRequestHandler.class, year, month)); } } diff --git a/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsDemo.java b/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsDemo.java new file mode 100644 index 000000000..96e86a357 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsDemo.java @@ -0,0 +1,77 @@ +/** ****************************************************************************** + * Copyright (c) 2025 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.statistics; + +import org.eclipse.openvsx.entities.PublisherStatistics; +import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.util.NamingUtil; +import org.eclipse.openvsx.util.TimeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + +@Component +public class PublisherStatisticsDemo { + + protected final Logger logger = LoggerFactory.getLogger(PublisherStatisticsDemo.class); + + private final RepositoryService repositories; + private final StatisticsService service; + + public PublisherStatisticsDemo(RepositoryService repositories, StatisticsService service) { + this.repositories = repositories; + this.service = service; + } + + @EventListener + public void applicationStarted(ApplicationStartedEvent event) { + var loginName = "amvanbaren"; + var user = repositories.findUserByLoginName("github", loginName); + if(user == null || !repositories.findPublisherStatisticsByUser(user).isEmpty()) { + return; + } + + var userDownloads = repositories.findMembershipDownloads(loginName); + userDownloads.forEach(i -> logger.info("{}.{}: {}", i.namespace(), i.extension(), i.downloadCount())); + var totals = userDownloads.stream() + .map((i) -> Map.entry(NamingUtil.toExtensionId(i.namespace(), i.extension()), 0L)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, HashMap::new)); + + var random = new Random(); + var now = TimeUtil.getCurrentUTC(); + for(var i = 6; i > 0; i--) { + var date = now.minusMonths(i); + var downloads = new HashMap(); + var totalDownloads = new HashMap(); + totals.keySet().forEach((key) -> { + var value = random.nextLong(1000L); + downloads.put(key, value); + var total = totals.get(key); + totalDownloads.put(key, total + value); + totals.put(key, total + value); + }); + + var statistics = new PublisherStatistics(); + statistics.setYear(date.getYear()); + statistics.setMonth(date.getMonthValue()); + statistics.setUser(user); + statistics.setExtensionDownloads(downloads); + statistics.setExtensionTotalDownloads(totalDownloads); + service.savePublisherStatistics(statistics); + } + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsJobRequestHandler.java new file mode 100644 index 000000000..711460b17 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/statistics/PublisherStatisticsJobRequestHandler.java @@ -0,0 +1,87 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ +package org.eclipse.openvsx.statistics; + +import jakarta.persistence.EntityManager; +import org.eclipse.openvsx.entities.PublisherStatistics; +import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.util.NamingUtil; +import org.jobrunr.jobs.lambdas.JobRequestHandler; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class PublisherStatisticsJobRequestHandler implements JobRequestHandler { + + private final RepositoryService repositories; + private final EntityManager entityManager; + private final StatisticsService service; + + public PublisherStatisticsJobRequestHandler(RepositoryService repositories, EntityManager entityManager, StatisticsService service) { + this.repositories = repositories; + this.entityManager = entityManager; + this.service = service; + } + + @Override + public void run(StatisticsJobRequest jobRequest) throws Exception { + var year = jobRequest.getYear(); + var month = jobRequest.getMonth(); + var prevDate = LocalDateTime.of(year, month, 1, 0, 0).minusMonths(1); + + var offset = 0; + var limit = 10000; + var userId = -1L; + var userDownloads = new ArrayList(); + var membershipDownloads = Collections.emptyList(); + do { + for(var membershipDownload : membershipDownloads) { + if(membershipDownload.userId() != userId) { + if(userId != -1L) { + var totalDownloads = userDownloads.stream() + .map(i -> Map.entry(NamingUtil.toExtensionId(i.namespace(), i.extension()), i.downloadCount())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + var prevStatistics = repositories.findPublisherStatisticsByYearAndMonthAndUserId(prevDate.getYear(), prevDate.getMonthValue(), userId); + var prevTotalDownloads = prevStatistics != null ? prevStatistics.getExtensionTotalDownloads() : Collections.emptyMap(); + var downloads = totalDownloads.entrySet().stream() + .map(e -> { + var prevDownloads = prevTotalDownloads.getOrDefault(e.getKey(), 0L); + return Map.entry(e.getKey(), e.getValue() - prevDownloads); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + var statistics = new PublisherStatistics(); + statistics.setYear(year); + statistics.setMonth(month); + statistics.setUser(entityManager.find(UserData.class, userId)); + statistics.setExtensionDownloads(downloads); + statistics.setExtensionTotalDownloads(totalDownloads); + service.savePublisherStatistics(statistics); + } + + userId = membershipDownload.userId(); + userDownloads.clear(); + } + + userDownloads.add(membershipDownload); + } + + membershipDownloads = repositories.findMembershipDownloads(offset, limit); + offset += limit; + } while(!membershipDownloads.isEmpty()); + } +} \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequest.java b/server/src/main/java/org/eclipse/openvsx/statistics/StatisticsJobRequest.java similarity index 63% rename from server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequest.java rename to server/src/main/java/org/eclipse/openvsx/statistics/StatisticsJobRequest.java index ee9d1e5d8..90f990cc0 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequest.java +++ b/server/src/main/java/org/eclipse/openvsx/statistics/StatisticsJobRequest.java @@ -7,23 +7,33 @@ * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ -package org.eclipse.openvsx.admin; +package org.eclipse.openvsx.statistics; import org.jobrunr.jobs.lambdas.JobRequest; import org.jobrunr.jobs.lambdas.JobRequestHandler; -public class AdminStatisticsJobRequest implements JobRequest { +public class StatisticsJobRequest> implements JobRequest { + private Class handler; private int year; private int month; - public AdminStatisticsJobRequest() {} + public StatisticsJobRequest() {} - public AdminStatisticsJobRequest(int year, int month) { + public StatisticsJobRequest(Class handler,int year, int month) { + this.handler = handler; this.year = year; this.month = month; } + public Class getHandler() { + return handler; + } + + public void setHandler(Class handler) { + this.handler = handler; + } + public int getYear() { return year; } @@ -41,7 +51,7 @@ public void setMonth(int month) { } @Override - public Class getJobRequestHandler() { - return AdminStatisticsJobRequestHandler.class; + public Class getJobRequestHandler() { + return this.handler; } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsService.java b/server/src/main/java/org/eclipse/openvsx/statistics/StatisticsService.java similarity index 72% rename from server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsService.java rename to server/src/main/java/org/eclipse/openvsx/statistics/StatisticsService.java index ed725ab28..b4a4b759d 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminStatisticsService.java +++ b/server/src/main/java/org/eclipse/openvsx/statistics/StatisticsService.java @@ -7,19 +7,20 @@ * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ -package org.eclipse.openvsx.admin; +package org.eclipse.openvsx.statistics; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.eclipse.openvsx.entities.AdminStatistics; +import org.eclipse.openvsx.entities.PublisherStatistics; import org.springframework.stereotype.Component; @Component -public class AdminStatisticsService { +public class StatisticsService { private final EntityManager entityManager; - public AdminStatisticsService(EntityManager entityManager) { + public StatisticsService(EntityManager entityManager) { this.entityManager = entityManager; } @@ -27,4 +28,9 @@ public AdminStatisticsService(EntityManager entityManager) { public void saveAdminStatistics(AdminStatistics statistics) { entityManager.persist(statistics); } + + @Transactional + public void savePublisherStatistics(PublisherStatistics statistics) { + entityManager.persist(statistics); + } } diff --git a/server/src/main/resources/db/migration/V1_52__Publisher_Statistics.sql b/server/src/main/resources/db/migration/V1_52__Publisher_Statistics.sql new file mode 100644 index 000000000..5d3ba7fc0 --- /dev/null +++ b/server/src/main/resources/db/migration/V1_52__Publisher_Statistics.sql @@ -0,0 +1,29 @@ +-- Create tables and constraints for PublisherStatistics entity +CREATE TABLE public.publisher_statistics( + id bigint NOT NULL, + user_data bigint NOT NULL, + year INT NOT NULL, + month INT NOT NULL +); + +ALTER TABLE ONLY public.publisher_statistics ADD CONSTRAINT publisher_statistics_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.publisher_statistics ADD CONSTRAINT fk_publisher_statistics_user_data FOREIGN KEY (user_data) REFERENCES public.user_data(id); +CREATE SEQUENCE publisher_statistics_seq INCREMENT 50 OWNED BY public.publisher_statistics.id; + +CREATE TABLE public.publisher_statistics_extension_downloads( + publisher_statistics_id BIGINT NOT NULL, + extension_identifier CHARACTER VARYING(255) NOT NULL, + downloads BIGINT NOT NULL +); + +ALTER TABLE ONLY public.publisher_statistics_extension_downloads + ADD CONSTRAINT publisher_statistics_extension_downloads_fkey FOREIGN KEY (publisher_statistics_id) REFERENCES publisher_statistics(id); + +CREATE TABLE public.publisher_statistics_extension_total_downloads( + publisher_statistics_id BIGINT NOT NULL, + extension_identifier CHARACTER VARYING(255) NOT NULL, + downloads BIGINT NOT NULL +); + +ALTER TABLE ONLY public.publisher_statistics_extension_total_downloads + ADD CONSTRAINT publisher_statistics_extension_total_downloads_fkey FOREIGN KEY (publisher_statistics_id) REFERENCES publisher_statistics(id); \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java index 5cf58c4ee..7848e4d0d 100644 --- a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java +++ b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java @@ -222,7 +222,11 @@ void testExecuteQueries() { () -> repositories.findLatestReplacement(1L, null, false, false), () -> repositories.findNotMigratedLocalNamespaceLogos(page), () -> repositories.findNotMigratedLocalFileResourceContent(page), - () -> repositories.findNotMigratedFileResourceTypeResource(page) + () -> repositories.findNotMigratedFileResourceTypeResource(page), + () -> repositories.findPublisherStatisticsByYearAndMonthAndUserId(2025, 1, userData.getId()), + () -> repositories.findPublisherStatisticsByUser(userData), + () -> repositories.findMembershipDownloads("loginName"), + () -> repositories.findMembershipDownloads(0, 1) ); // check that we did not miss anything diff --git a/server/src/test/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandlerTest.java b/server/src/test/java/org/eclipse/openvsx/statistics/StatisticsJobRequestHandlerTest.java similarity index 91% rename from server/src/test/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandlerTest.java rename to server/src/test/java/org/eclipse/openvsx/statistics/StatisticsJobRequestHandlerTest.java index 466a015ff..8b79b108d 100644 --- a/server/src/test/java/org/eclipse/openvsx/admin/AdminStatisticsJobRequestHandlerTest.java +++ b/server/src/test/java/org/eclipse/openvsx/statistics/StatisticsJobRequestHandlerTest.java @@ -7,10 +7,13 @@ * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ -package org.eclipse.openvsx.admin; +package org.eclipse.openvsx.statistics; import org.eclipse.openvsx.entities.AdminStatistics; import org.eclipse.openvsx.repositories.RepositoryService; +import org.eclipse.openvsx.statistics.StatisticsJobRequest; +import org.eclipse.openvsx.statistics.AdminStatisticsJobRequestHandler; +import org.eclipse.openvsx.statistics.StatisticsService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; @@ -23,13 +26,13 @@ import java.util.Map; @ExtendWith(SpringExtension.class) -class AdminStatisticsJobRequestHandlerTest { +class StatisticsJobRequestHandlerTest { @MockBean RepositoryService repositories; @MockBean - AdminStatisticsService service; + StatisticsService service; @Autowired AdminStatisticsJobRequestHandler handler; @@ -38,7 +41,7 @@ class AdminStatisticsJobRequestHandlerTest { void testAdminStatisticsJobRequestHandler() throws Exception { var expectedStatistics = mockAdminStatistics(); - var request = new AdminStatisticsJobRequest(2023, 11); + var request = new StatisticsJobRequest(AdminStatisticsJobRequestHandler.class, 2023, 11); handler.run(request); Mockito.verify(service).saveAdminStatistics(expectedStatistics); } @@ -52,7 +55,7 @@ void testAdminStatisticsJobRequestHandlerWithPreviousStatistics() throws Excepti prevStatistics.setDownloadsTotal(5000); Mockito.when(repositories.findAdminStatisticsByYearAndMonth(2023, 10)).thenReturn(prevStatistics); - var request = new AdminStatisticsJobRequest(2023, 11); + var request = new StatisticsJobRequest(AdminStatisticsJobRequestHandler.class, 2023, 11); handler.run(request); Mockito.verify(service).saveAdminStatistics(expectedStatistics); } @@ -62,7 +65,7 @@ static class TestConfig { @Bean AdminStatisticsJobRequestHandler adminStatisticsJobRequestHandler( RepositoryService repositories, - AdminStatisticsService service + StatisticsService service ) { return new AdminStatisticsJobRequestHandler(repositories, service); } diff --git a/webui/package.json b/webui/package.json index 4a13a8bd9..333ee50c9 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "openvsx-webui", - "version": "0.15.0", + "version": "0.16.0-next.e4b3022d", "description": "User interface for Eclipse Open VSX", "keywords": [ "react", @@ -57,7 +57,8 @@ "react-helmet-async": "^1.3.0", "react-infinite-scroller": "^1.2.6", "react-router": "^6.14.2", - "react-router-dom": "^6.14.1" + "react-router-dom": "^6.14.1", + "recharts": "^2.15.0" }, "resolutions": { "@types/react": "^18.2.14" diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts index fdb67b524..eaee0a8d5 100644 --- a/webui/src/extension-registry-service.ts +++ b/webui/src/extension-registry-service.ts @@ -11,7 +11,8 @@ import { Extension, UserData, ExtensionCategory, ExtensionReviewList, PersonalAccessToken, SearchResult, NewReview, SuccessResult, ErrorResult, CsrfTokenJson, isError, Namespace, NamespaceDetails, MembershipRole, SortBy, - SortOrder, UrlString, NamespaceMembershipList, PublisherInfo, SearchEntry, RegistryVersion + SortOrder, UrlString, NamespaceMembershipList, PublisherInfo, SearchEntry, RegistryVersion, + PublisherStatistics } from './extension-registry-types'; import { createAbsoluteURL, addQuery } from './utils'; import { sendRequest, ErrorResponse } from './server-request'; @@ -425,6 +426,23 @@ export class ExtensionRegistryService { const endpoint = createAbsoluteURL([this.serverUrl, 'can-login']); return sendRequest({ abortController, endpoint }); } + + async getStatistics(abortController: AbortController): Promise> { + const csrfResponse = await this.getCsrfToken(abortController); + const headers: Record = {}; + if (!isError(csrfResponse)) { + const csrfToken = csrfResponse as CsrfTokenJson; + headers[csrfToken.header] = csrfToken.value; + } + + return sendRequest({ + abortController, + method: 'GET', + credentials: true, + headers: headers, + endpoint: createAbsoluteURL([this.serverUrl, 'user', 'statistics']) + }); + } } export interface AdminService { diff --git a/webui/src/extension-registry-types.ts b/webui/src/extension-registry-types.ts index cd8b862a8..dd0e0cfd7 100644 --- a/webui/src/extension-registry-types.ts +++ b/webui/src/extension-registry-types.ts @@ -254,3 +254,10 @@ export interface RegistryVersion { export type MembershipRole = 'contributor' | 'owner'; export type SortBy = 'relevance' | 'timestamp' | 'rating' | 'downloadCount'; export type SortOrder = 'asc' | 'desc'; + +export interface PublisherStatistics { + year: number + month: number + extensionDownloads: Record; + extensionTotalDownloads: Record; +} \ No newline at end of file diff --git a/webui/src/pages/user/user-publisher-statistics.tsx b/webui/src/pages/user/user-publisher-statistics.tsx new file mode 100644 index 000000000..93c5da982 --- /dev/null +++ b/webui/src/pages/user/user-publisher-statistics.tsx @@ -0,0 +1,106 @@ +/******************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import React, { FunctionComponent, useMemo } from 'react'; +import { PublisherStatistics } from '../../extension-registry-types'; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Paper, Stack, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { red, pink, purple, deepPurple, indigo, blue, lightBlue, cyan, teal, green, lightGreen, yellow, orange, deepOrange, brown, blueGrey } from '@mui/material/colors'; + +interface UserPublisherStatisticsProps { + statistics: PublisherStatistics[]; +} + +const colors = [ + red[500], + cyan[500], + pink[500], + teal[500], + purple[500], + green[500], + deepPurple[500], + lightGreen[500], + indigo[500], + yellow[500], + blue[500], + orange[500], + lightBlue[500], + deepOrange[500], + brown[500], + blueGrey[500] +]; + +interface BarChartCardProps { + title: string + data: Array> + bars: string[] +} + +const BarChartCard: FunctionComponent = props => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + return + {props.title} + + + + + + + + { + [...props.bars].map((value, index) => ) + } + + + ; +}; + +export const UserPublisherStatistics: FunctionComponent = ({ statistics }) => { + + const charts = useMemo((): BarChartCardProps[] => { + const relativeSeries = new Set(); + const absoluteSeries = new Set(); + const relativeDownloads: Array> = []; + const absoluteDownloads: Array> = []; + + for (const stat of statistics) { + const { year, month, extensionDownloads, extensionTotalDownloads } = stat; + const name = `${month}/${year}`; + relativeDownloads.push({ ...extensionDownloads, name }); + absoluteDownloads.push({ ...extensionTotalDownloads, name }); + Object.keys(extensionDownloads).forEach((key) => relativeSeries.add(key)); + Object.keys(extensionTotalDownloads).forEach((key) => absoluteSeries.add(key)); + } + + return [ + { title: 'Relative Downloads', data: relativeDownloads, bars: Array.from(relativeSeries).sort() }, + { title: 'Absolute Downloads', data: absoluteDownloads, bars: Array.from(absoluteSeries).sort() } + ]; + }, [statistics]); + + + + return + { + charts.map(({ title, data, bars }) => ) + } + ; +}; + diff --git a/webui/src/pages/user/user-setting-tabs.tsx b/webui/src/pages/user/user-setting-tabs.tsx index fda9570e9..ca7cbea35 100644 --- a/webui/src/pages/user/user-setting-tabs.tsx +++ b/webui/src/pages/user/user-setting-tabs.tsx @@ -42,6 +42,7 @@ export const UserSettingTabs = (): ReactElement => { + ); }; \ No newline at end of file diff --git a/webui/src/pages/user/user-settings-statistics.tsx b/webui/src/pages/user/user-settings-statistics.tsx new file mode 100644 index 000000000..ddefcb511 --- /dev/null +++ b/webui/src/pages/user/user-settings-statistics.tsx @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import { Box, Typography } from '@mui/material'; +import React, { FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; +import { DelayedLoadIndicator } from '../../components/delayed-load-indicator'; +import { MainContext } from '../../context'; +import { isError, PublisherStatistics } from '../../extension-registry-types'; +import { UserPublisherStatistics } from './user-publisher-statistics'; + +export const UserSettingsStatistics: FunctionComponent = () => { + + const [loading, setLoading] = useState(true); + const [statistics, setStatistics] = useState([]); + const { user, service, handleError } = useContext(MainContext); + const abortController = useRef(new AbortController()); + + useEffect(() => { + updateExtensions(); + return () => { + abortController.current.abort(); + }; + }, []); + + const updateExtensions = async (): Promise => { + if (!user) { + return; + } + try { + const response = await service.getStatistics(abortController.current); + if (isError(response)) { + throw response; + } + + setStatistics(response as PublisherStatistics[]); + setLoading(false); + } catch (err) { + handleError(err); + setLoading(false); + } + }; + + return <> + + + Statistics + + + + + { + statistics.length > 0 + ? + : No statistics available yet. + } + +; +}; \ No newline at end of file diff --git a/webui/src/pages/user/user-settings.tsx b/webui/src/pages/user/user-settings.tsx index ed01eb91c..a4c3c56e1 100644 --- a/webui/src/pages/user/user-settings.tsx +++ b/webui/src/pages/user/user-settings.tsx @@ -21,6 +21,7 @@ import { UserSettingsNamespaces } from './user-settings-namespaces'; import { UserSettingsExtensions } from './user-settings-extensions'; import { MainContext } from '../../context'; import { UserData } from '../../extension-registry-types'; +import { UserSettingsStatistics } from './user-settings-statistics'; export namespace UserSettingsRoutes { export const ROOT = createRoute(['user-settings']); @@ -46,6 +47,8 @@ export const UserSettings: FunctionComponent = props => { return ; case 'extensions': return ; + case 'statistics': + return ; default: return null; } diff --git a/webui/yarn.lock b/webui/yarn.lock index bc7a05ce9..bbdeb8d95 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -278,7 +278,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3": version: 7.24.6 resolution: "@babel/runtime@npm:7.24.6" dependencies: @@ -287,6 +287,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.6, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10/9f4ea1c1d566c497c052d505587554e782e021e6ccd302c2ad7ae8291c8e16e3f19d4a7726fb64469e057779ea2081c28b7dbefec6d813a22f08a35712c0f699 + languageName: node + linkType: hard + "@babel/template@npm:^7.25.0": version: 7.25.0 resolution: "@babel/template@npm:7.25.0" @@ -575,41 +584,41 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.0.0": - version: 1.6.2 - resolution: "@floating-ui/core@npm:1.6.2" +"@floating-ui/core@npm:^1.6.0": + version: 1.6.8 + resolution: "@floating-ui/core@npm:1.6.8" dependencies: - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/5c940ef3d397aa23f859ecb033bda408dde20820af3f82090a889c35a99826cfaa7864e8131b9906a26b2c04f31fa468538a28d0715b34de541e0776e0f82d03 + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10/87d52989c3d2cc80373bc153b7a40814db3206ce7d0b2a2bdfb63e2ff39ffb8b999b1b0ccf28e548000ebf863bf16e2bed45eab4c4d287a5dbe974ef22368d82 languageName: node linkType: hard "@floating-ui/dom@npm:^1.0.0": - version: 1.6.5 - resolution: "@floating-ui/dom@npm:1.6.5" + version: 1.6.12 + resolution: "@floating-ui/dom@npm:1.6.12" dependencies: - "@floating-ui/core": "npm:^1.0.0" - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/d421e7f239e9af5a2a4c7a560c29b8ce1f29398c411c8e3bd0c33a2ce800e13a378749a1606e4f6b460830f4007c459792534821013262d24d1385476b1ba48d + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10/5c8e5fdcd3843140a606ab6dc6c12ad740f44e66b898966ef877393faaede0bbe14586e1049e2c2f08856437da8847e884a2762e78275fefa65a5a9cd71e580d languageName: node linkType: hard "@floating-ui/react-dom@npm:^2.0.8": - version: 2.1.0 - resolution: "@floating-ui/react-dom@npm:2.1.0" + version: 2.1.2 + resolution: "@floating-ui/react-dom@npm:2.1.2" dependencies: "@floating-ui/dom": "npm:^1.0.0" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/15be0714379c271ff01347e7c9bcdba96d6b39f3960697380e23de9b9d59fb91ba07bc75b8bdb12d72da7a9272191a9ce73f843a0d5f89939caa9f3137acd8ec + checksum: 10/2a67dc8499674e42ff32c7246bded185bb0fdd492150067caf9568569557ac4756a67787421d8604b0f241e5337de10762aee270d9aeef106d078a0ff13596c4 languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0": - version: 0.2.2 - resolution: "@floating-ui/utils@npm:0.2.2" - checksum: 10/28d900d2f0876b40c7090f55724700eeac608862e59110b7b14731223218cf7ce125b2091f34103edf4b0f779166151bbca21256b856236235a2be996548ed38 +"@floating-ui/utils@npm:^0.2.8": + version: 0.2.8 + resolution: "@floating-ui/utils@npm:0.2.8" + checksum: 10/3e3ea3b2de06badc4baebdf358b3dbd77ccd9474a257a6ef237277895943db2acbae756477ec64de65a2a1436d94aea3107129a1feeef6370675bf2b161c1abc languageName: node linkType: hard @@ -717,28 +726,6 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.40": - version: 5.0.0-beta.40 - resolution: "@mui/base@npm:5.0.0-beta.40" - dependencies: - "@babel/runtime": "npm:^7.23.9" - "@floating-ui/react-dom": "npm:^2.0.8" - "@mui/types": "npm:^7.2.14" - "@mui/utils": "npm:^5.15.14" - "@popperjs/core": "npm:^2.11.8" - clsx: "npm:^2.1.0" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/ebee3d9e1136710dcb2af5828acc6bd8d54f6b124785d011585c2665a48dc66e35ccb344d5ebc7fd8bfd776cccb8ea434911f151a62bee193677ee9dc67fc7fc - languageName: node - linkType: hard - "@mui/base@npm:^5.0.0-beta.9": version: 5.0.0-dev.20240529-082515-213b5e33ab resolution: "@mui/base@npm:5.0.0-dev.20240529-082515-213b5e33ab" @@ -761,51 +748,51 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.15.19": - version: 5.15.19 - resolution: "@mui/core-downloads-tracker@npm:5.15.19" - checksum: 10/32dd442d72a4cf4abea0e5c0a325707c3f8aba16b7b40ed674da2c068ed10d686f1941240e527407d685e00ed12931c331d99265e1ed570630c856ffbe291c23 +"@mui/core-downloads-tracker@npm:^5.16.9": + version: 5.16.9 + resolution: "@mui/core-downloads-tracker@npm:5.16.9" + checksum: 10/25e7cf746627e12671e2bae4ea8f81967fbb7e05188c268052104d05f249eea1baa3f3d97f66d1242112afa944a7b025a331527392797d620acbcc1dde23a3df languageName: node linkType: hard "@mui/icons-material@npm:^5.13.7": - version: 5.15.19 - resolution: "@mui/icons-material@npm:5.15.19" + version: 5.16.9 + resolution: "@mui/icons-material@npm:5.16.9" dependencies: "@babel/runtime": "npm:^7.23.9" peerDependencies: "@mui/material": ^5.0.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/f2d47ed7ffba7e28d24feb4160b65f814135ed3b96e10aa13949707a6e41e39695badd04c822c4e937892ea630a3640ddd0ff7b073f75def3f28bad2c9e5271b + checksum: 10/5b5ceee66c82f8bcd467c9d13aec14592868b0cf981573c609a3e17bdb1968bfb47b459dd54d33e8bf93838387998aba89887ccb910d0cd0375adea406ce8c0f languageName: node linkType: hard "@mui/material@npm:^5.13.7": - version: 5.15.19 - resolution: "@mui/material@npm:5.15.19" + version: 5.16.9 + resolution: "@mui/material@npm:5.16.9" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/base": "npm:5.0.0-beta.40" - "@mui/core-downloads-tracker": "npm:^5.15.19" - "@mui/system": "npm:^5.15.15" - "@mui/types": "npm:^7.2.14" - "@mui/utils": "npm:^5.15.14" + "@mui/core-downloads-tracker": "npm:^5.16.9" + "@mui/system": "npm:^5.16.8" + "@mui/types": "npm:^7.2.15" + "@mui/utils": "npm:^5.16.8" + "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.10" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" + react-is: "npm:^18.3.1" react-transition-group: "npm:^4.4.5" peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@emotion/react": optional: true @@ -813,30 +800,30 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/92618aaefc85b4d4a6012dba48fe4b4936db45c1afd3436b148c81d8b8d0a001c57fd654bd101f94077979f1cf4e3ad5a7e5dd24a6f1b666f3d2e23d75a63f84 + checksum: 10/0bbdb2a0309e70b7e43f04c0af19dbe3d4d5180f69aecd599182d92e21869a449cba8f94dfc280c0e6aa26d3b5a0c72c424e5c445ee4a2fdd01e79e7c24639e1 languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.14": - version: 5.15.14 - resolution: "@mui/private-theming@npm:5.15.14" +"@mui/private-theming@npm:^5.16.8": + version: 5.16.8 + resolution: "@mui/private-theming@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/utils": "npm:^5.15.14" + "@mui/utils": "npm:^5.16.8" prop-types: "npm:^15.8.1" peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/6a14311ed53ee4adccfe0ba93275b43773d22fdd10c0d4ba680b9368fc0616a5e0f38f29d2080bcd7e4ed79123047e5f245c403d3fd822e960a97762be65218d + checksum: 10/7bd512d9024e3333473cbaba341835803abfc9ea1f37380ccf48e8ef719fef15162264eedddc7bb9cade56c245875559f01a5a0fb5bce80044b4be3a3c0aa690 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.15.14": - version: 5.15.14 - resolution: "@mui/styled-engine@npm:5.15.14" +"@mui/styled-engine@npm:^5.16.8": + version: 5.16.8 + resolution: "@mui/styled-engine@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/cache": "npm:^11.11.0" @@ -845,33 +832,33 @@ __metadata: peerDependencies: "@emotion/react": ^11.4.1 "@emotion/styled": ^11.3.0 - react: ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@emotion/react": optional: true "@emotion/styled": optional: true - checksum: 10/2a5e03bb20502aef94cfb908898c50abb769192deb32d7f4237039683ce5266104cdc4055a7f0a8342aa62447d52b7439a4f2d0dda0fa6709c227c3621468cab + checksum: 10/4b8a484dd10c36e26faf74094b5de5507a5c81faf369e8e2bdd74ceb6b1d7c45b1a69ca43e29de4700ccc13f8be15d61f5d1a85373ab3b7f5a7a64f6ae2ddc3a languageName: node linkType: hard -"@mui/system@npm:^5.15.15": - version: 5.15.15 - resolution: "@mui/system@npm:5.15.15" +"@mui/system@npm:^5.16.8": + version: 5.16.8 + resolution: "@mui/system@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/private-theming": "npm:^5.15.14" - "@mui/styled-engine": "npm:^5.15.14" - "@mui/types": "npm:^7.2.14" - "@mui/utils": "npm:^5.15.14" + "@mui/private-theming": "npm:^5.16.8" + "@mui/styled-engine": "npm:^5.16.8" + "@mui/types": "npm:^7.2.15" + "@mui/utils": "npm:^5.16.8" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@emotion/react": optional: true @@ -879,55 +866,59 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/90a84ad0bc1b401b6e53b13fe9cfe8a34668e84885d391abf5ab80b3cd0f37370be25cb40af253cdd468746386282fed24964315933fcb28d2d6e62de0db7bf1 + checksum: 10/f78b5d7a510eb6c53b50fd2d39b2d13feca0f8460b4a3376c2cff52a6e4fe1800473f01ba7a0f55b8238fdae99547f5f82049f3670e077c0fe31b6dfd9324dd3 languageName: node linkType: hard -"@mui/types@npm:^7.2.14, @mui/types@npm:^7.2.14-dev.20240529-082515-213b5e33ab": - version: 7.2.14 - resolution: "@mui/types@npm:7.2.14" +"@mui/types@npm:^7.2.14-dev.20240529-082515-213b5e33ab, @mui/types@npm:^7.2.15, @mui/types@npm:^7.2.19": + version: 7.2.19 + resolution: "@mui/types@npm:7.2.19" peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b10cca8f63ea522be4f7c185acd1f4d031947e53824cbf9dc5649c165bcfa8a2749e83fd0bd1809b8e2698f58638ab2b4ce03550095989189d14434ea5c6c0b6 + checksum: 10/a23bc280c0722527ce5e264b0dcb44271441e4016eb2285acc1f0d236cf78c73ecc3ec7abba81876c2eadf45b905b55eb26e0e824ea6afc233efce2ef5a34f7d languageName: node linkType: hard -"@mui/utils@npm:^5.15.14": - version: 5.15.14 - resolution: "@mui/utils@npm:5.15.14" +"@mui/utils@npm:^5.16.8": + version: 5.16.8 + resolution: "@mui/utils@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" - "@types/prop-types": "npm:^15.7.11" + "@mui/types": "npm:^7.2.15" + "@types/prop-types": "npm:^15.7.12" + clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" + react-is: "npm:^18.3.1" peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b3cbe2d0aa7ec65969752dababc39fc6e0b8bb1a9cf8b9bac42ca40e3dd3eaa59b79765bd259019318acc7421d64b9f421bc67e776a581d7c9da6a1c0c50bfbc + checksum: 10/b091f069a554eb7c691c362ec56c68c5939ce789f3b6d3b94a1718b3c0c7af28eb14aaf45cc8b10e32eb550df156e60f595b4d58f67f2bb34749768b41bd4502 languageName: node linkType: hard "@mui/utils@npm:^6.0.0-dev.20240529-082515-213b5e33ab": - version: 6.0.0-dev.20240529-082515-213b5e33ab - resolution: "@mui/utils@npm:6.0.0-dev.20240529-082515-213b5e33ab" + version: 6.1.10 + resolution: "@mui/utils@npm:6.1.10" dependencies: - "@babel/runtime": "npm:^7.24.6" - "@types/prop-types": "npm:^15.7.12" + "@babel/runtime": "npm:^7.26.0" + "@mui/types": "npm:^7.2.19" + "@types/prop-types": "npm:^15.7.13" + clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" + react-is: "npm:^18.3.1" peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/fa785f4cdc6c31c20279f955a0442462c8d73ae7e99a5277d6a6a3c34a1e522fc254e775493969308d25476f1854479cb3fe1cf5206ee2937d1eb55fc3bab904 + checksum: 10/d3c5bdaca8637623b329744890bb57b51645bff0f005408c04a02bd1f7f1af85eb9b82e628b8b5abc1f9c525a49d6eadbab751491a8e92d7066d255eb85adab4 languageName: node linkType: hard @@ -1094,6 +1085,75 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:^3.0.3": + version: 3.2.1 + resolution: "@types/d3-array@npm:3.2.1" + checksum: 10/4a9ecacaa859cff79e10dcec0c79053f027a4749ce0a4badeaff7400d69a9c44eb8210b147916b6ff5309be049030e7d68a0e333294ff3fa11c44aa1af4ba458 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.3 + resolution: "@types/d3-color@npm:3.1.3" + checksum: 10/1cf0f512c09357b25d644ab01b54200be7c9b15c808333b0ccacf767fff36f17520b2fcde9dad45e1bd7ce84befad39b43da42b4fded57680fa2127006ca3ece + languageName: node + linkType: hard + +"@types/d3-ease@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/d3-ease@npm:3.0.2" + checksum: 10/d8f92a8a7a008da71f847a16227fdcb53a8938200ecdf8d831ab6b49aba91e8921769761d3bfa7e7191b28f62783bfd8b0937e66bae39d4dd7fb0b63b50d4a94 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:^3.0.1": + version: 3.0.4 + resolution: "@types/d3-interpolate@npm:3.0.4" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10/72a883afd52c91132598b02a8cdfced9e783c54ca7e4459f9e29d5f45d11fb33f2cabc844e42fd65ba6e28f2a931dcce1add8607d2f02ef6fb8ea5b83ae84127 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.1.0 + resolution: "@types/d3-path@npm:3.1.0" + checksum: 10/7348d65c9b37c7023590d4e5ef11e37f9eee62df9fa23e0758da1fbd66a1cbff40e37cbe0b85e9388ab900451e9c18a5a973469e9fd725c8c85c4a3f84647b9d + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.2": + version: 4.0.8 + resolution: "@types/d3-scale@npm:4.0.8" + dependencies: + "@types/d3-time": "npm:*" + checksum: 10/376e4f2199ee6db70906651587a4521976920fa5eaa847a976c434e7a8171cbfeeab515cc510c5130b1f64fcf95b9750a7fd21dfc0a40fc3398641aa7dd4e7e2 + languageName: node + linkType: hard + +"@types/d3-shape@npm:^3.1.0": + version: 3.1.6 + resolution: "@types/d3-shape@npm:3.1.6" + dependencies: + "@types/d3-path": "npm:*" + checksum: 10/75abf403ec5b8c11e761256aa6b3546533d61e2e12f15c82bed6b606e963dcdfb9868a2038c46099173c8830423b35ddaf14d1162f96ad9da18a2e90b0fa7d25 + languageName: node + linkType: hard + +"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/d3-time@npm:3.0.4" + checksum: 10/b1eb4255066da56023ad243fd4ae5a20462d73bd087a0297c7d49ece42b2304a4a04297568c604a38541019885b2bc35a9e0fd704fad218e9bc9c5f07dc685ce + languageName: node + linkType: hard + +"@types/d3-timer@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/d3-timer@npm:3.0.2" + checksum: 10/1643eebfa5f4ae3eb00b556bbc509444d88078208ec2589ddd8e4a24f230dd4cf2301e9365947e70b1bee33f63aaefab84cd907822aae812b9bc4871b98ab0e1 + languageName: node + linkType: hard + "@types/dompurify@npm:^3.0.2": version: 3.0.5 resolution: "@types/dompurify@npm:3.0.5" @@ -1230,13 +1290,20 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.11, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.5": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5": version: 15.7.12 resolution: "@types/prop-types@npm:15.7.12" checksum: 10/ac16cc3d0a84431ffa5cfdf89579ad1e2269549f32ce0c769321fdd078f84db4fbe1b461ed5a1a496caf09e637c0e367d600c541435716a55b1d9713f5035dfe languageName: node linkType: hard +"@types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.13": + version: 15.7.14 + resolution: "@types/prop-types@npm:15.7.14" + checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d + languageName: node + linkType: hard + "@types/punycode@npm:^2.1.0": version: 2.1.4 resolution: "@types/punycode@npm:2.1.4" @@ -1306,7 +1373,16 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^4.4.10, @types/react-transition-group@npm:^4.4.6": +"@types/react-transition-group@npm:^4.4.10": + version: 4.4.11 + resolution: "@types/react-transition-group@npm:4.4.11" + dependencies: + "@types/react": "npm:*" + checksum: 10/a7f4de6e5f57d9fcdea027e22873c633f96a803c96d422db8b99a45c36a9cceb7882d152136bbc31c7158fc1827e37aea5070d369724bb71dd11b5687332bc4d + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.6": version: 4.4.10 resolution: "@types/react-transition-group@npm:4.4.10" dependencies: @@ -2296,7 +2372,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.1.0, clsx@npm:^2.1.1": +"clsx@npm:^2.0.0, clsx@npm:^2.1.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: 10/cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 @@ -2480,6 +2556,99 @@ __metadata: languageName: node linkType: hard +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: "npm:1 - 2" + checksum: 10/5800c467f89634776a5977f6dae3f4e127d91be80f1d07e3e6e35303f9de93e6636d014b234838eea620f7469688d191b3f41207a30040aab750a63c97ec1d7c + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 10/536ba05bfd9f4fcd6fa289b5974f5c846b21d186875684637e22bf6855e6aba93e24a2eb3712985c6af3f502fbbfa03708edb72f58142f626241a8a17258e545 + languageName: node + linkType: hard + +"d3-ease@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 10/985d46e868494e9e6806fedd20bad712a50dcf98f357bf604a843a9f6bc17714a657c83dd762f183173dcde983a3570fa679b2bc40017d40b24163cdc4167796 + languageName: node + linkType: hard + +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: 10/a0fe23d2575f738027a3db0ce57160e5a473ccf24808c1ed46d45ef4f3211076b34a18b585547d34e365e78dcc26dd4ab15c069731fc4b1c07a26bfced09ea31 + languageName: node + linkType: hard + +"d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 10/988d66497ef5c190cf64f8c80cd66e1e9a58c4d1f8932d776a8e3ae59330291795d5a342f5a97602782ccbef21a5df73bc7faf1f0dc46a5145ba6243a82a0f0e + languageName: node + linkType: hard + +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 10/8e97a9ab4930a05b18adda64cf4929219bac913a5506cf8585631020253b39309549632a5cbeac778c0077994442ddaaee8316ee3f380e7baf7566321b84e76a + languageName: node + linkType: hard + +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: "npm:2.10.0 - 3" + d3-format: "npm:1 - 3" + d3-interpolate: "npm:1.2.0 - 3" + d3-time: "npm:2.1.1 - 3" + d3-time-format: "npm:2 - 4" + checksum: 10/e2dc4243586eae2a0fdf91de1df1a90d51dfacb295933f0ca7e9184c31203b01436bef69906ad40f1100173a5e6197ae753cb7b8a1a8fcfda43194ea9cad6493 + languageName: node + linkType: hard + +"d3-shape@npm:^3.1.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: "npm:^3.1.0" + checksum: 10/2e861f4d4781ee8abd85d2b435f848d667479dcf01a4e0db3a06600a5bdeddedb240f88229ec7b3bf7fa300c2b3526faeaf7e75f9a24dbf4396d3cc5358ff39d + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: "npm:1 - 3" + checksum: 10/ffc0959258fbb90e3890bfb31b43b764f51502b575e87d0af2c85b85ac379120d246914d07fca9f533d1bcedc27b2841d308a00fd64848c3e2cad9eff5c9a0aa + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:^3.0.0": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: "npm:2 - 3" + checksum: 10/c110bed295ce63e8180e45b82a9b0ba114d5f33ff315871878f209c1a6d821caa505739a2b07f38d1396637155b8e7372632dacc018e11fbe8ceef58f6af806d + languageName: node + linkType: hard + +"d3-timer@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 10/004128602bb187948d72c7dc153f0f063f38ac7a584171de0b45e3a841ad2e17f1e40ad396a4af9cce5551b6ab4a838d5246d23492553843d9da4a4050a911e2 + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.1": version: 1.0.1 resolution: "data-view-buffer@npm:1.0.1" @@ -2565,6 +2734,13 @@ __metadata: languageName: node linkType: hard +"decimal.js-light@npm:^2.4.1": + version: 2.5.1 + resolution: "decimal.js-light@npm:2.5.1" + checksum: 10/6360911e31221a9b8b90e23020aa969d104e182c5c6518589cdfedc3ced31bf1f19cf931e265bd451ae6ee3a35ee15e81f5456a86813606fda96f8374616688f + languageName: node + linkType: hard + "deep-eql@npm:^4.1.3": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -3161,6 +3337,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^4.0.1": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10/8030029382404942c01d0037079f1b1bc8fed524b5849c237b80549b01e2fc49709e1d0c557fa65ca4498fc9e24cff1475ef7b855121fcc15f9d61f93e282346 + languageName: node + linkType: hard + "events@npm:^3.2.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -3230,6 +3413,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^5.0.1": + version: 5.0.1 + resolution: "fast-equals@npm:5.0.1" + checksum: 10/9dc1ef767903600e5694a89a787782fc3a4f56cc04d235afc13616c848c33563b8f415b1c6a248b2a236424f624aa1a7513f46b7fa69590a640e7784f1296bce + languageName: node + linkType: hard + "fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" @@ -3883,6 +4073,13 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 10/873e0e7fcfe32f999aa0997a0b648b1244508e56e3ea6b8259b5245b50b5eeb3853fba221f96692bd6d1def501da76c32d64a5cb22a0b26cdd9b445664f805e0 + languageName: node + linkType: hard + "interpret@npm:^3.1.1": version: 3.1.1 resolution: "interpret@npm:3.1.1" @@ -5051,6 +5248,7 @@ __metadata: react-infinite-scroller: "npm:^1.2.6" react-router: "npm:^6.14.2" react-router-dom: "npm:^6.14.1" + recharts: "npm:^2.15.0" rimraf: "npm:^6.0.1" source-map-loader: "npm:^4.0.1" style-loader: "npm:^3.3.3" @@ -5542,7 +5740,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.2.0": +"react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 @@ -5573,6 +5771,20 @@ __metadata: languageName: node linkType: hard +"react-smooth@npm:^4.0.0": + version: 4.0.4 + resolution: "react-smooth@npm:4.0.4" + dependencies: + fast-equals: "npm:^5.0.1" + prop-types: "npm:^15.8.1" + react-transition-group: "npm:^4.4.5" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/cc5593356d154253f61a2c0b7b2fa8a979527495e2fe47c4252628d86e93c72c75df988c5438867d373de4e5a47d871ab9262474c02e66c411f94f047ecb5b0f + languageName: node + linkType: hard + "react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -5606,6 +5818,34 @@ __metadata: languageName: node linkType: hard +"recharts-scale@npm:^0.4.4": + version: 0.4.5 + resolution: "recharts-scale@npm:0.4.5" + dependencies: + decimal.js-light: "npm:^2.4.1" + checksum: 10/6e1118635018bd0622b5e978e56a8764ced5741140709e025c5989a0cb40c4b0bebb7c4e231c11ab8d6127a85fef8c68d92662c6f3c22af9551737a767cea014 + languageName: node + linkType: hard + +"recharts@npm:^2.15.0": + version: 2.15.0 + resolution: "recharts@npm:2.15.0" + dependencies: + clsx: "npm:^2.0.0" + eventemitter3: "npm:^4.0.1" + lodash: "npm:^4.17.21" + react-is: "npm:^18.3.1" + react-smooth: "npm:^4.0.0" + recharts-scale: "npm:^0.4.4" + tiny-invariant: "npm:^1.3.1" + victory-vendor: "npm:^36.6.8" + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/a34d2b1d9b8f8c56691e2b0fbf1d1282753d8bcd4ea285e3f575d12e645729cc907617103b964129c82984fdaf6d26ba855729947b822ca1c423e9bc97af7c46 + languageName: node + linkType: hard + "rechoir@npm:^0.8.0": version: 0.8.0 resolution: "rechoir@npm:0.8.0" @@ -6307,6 +6547,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.1": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 10/5e185c8cc2266967984ce3b352a4e57cb89dad5a8abb0dea21468a6ecaa67cd5bb47a3b7a85d08041008644af4f667fb8b6575ba38ba5fb00b3b5068306e59fe + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -6600,6 +6847,28 @@ __metadata: languageName: node linkType: hard +"victory-vendor@npm:^36.6.8": + version: 36.9.2 + resolution: "victory-vendor@npm:36.9.2" + dependencies: + "@types/d3-array": "npm:^3.0.3" + "@types/d3-ease": "npm:^3.0.0" + "@types/d3-interpolate": "npm:^3.0.1" + "@types/d3-scale": "npm:^4.0.2" + "@types/d3-shape": "npm:^3.1.0" + "@types/d3-time": "npm:^3.0.0" + "@types/d3-timer": "npm:^3.0.0" + d3-array: "npm:^3.1.6" + d3-ease: "npm:^3.0.1" + d3-interpolate: "npm:^3.0.1" + d3-scale: "npm:^4.0.2" + d3-shape: "npm:^3.1.0" + d3-time: "npm:^3.0.0" + d3-timer: "npm:^3.0.1" + checksum: 10/db67b3d9b8070d4eae4122edc72be7067b4e32363340cdd4d5b628e7dd65bea0c7c5b4116016658d223adaa575bcc6b7b3a71507aa4f34b2609ed61dbfbba1ea + languageName: node + linkType: hard + "watchpack@npm:^2.4.1": version: 2.4.1 resolution: "watchpack@npm:2.4.1"