From 5fa7bb23523490359fc107326831ab7d7f9d397b Mon Sep 17 00:00:00 2001 From: Sonatype Zion Date: Fri, 22 Mar 2024 12:52:46 +0000 Subject: [PATCH] upstream: b=main,r=ff6edd8951f81b4573f844984e3b37c8c543b09a,t=2024-03-22-1242-42785 --- .../osgi/ProStarterNexusEdition.java | 1 + .../nexus/common/analytics/ContentUsage.java | 30 ++++++++++ .../{app => analytics}/UsageMetrics.java | 2 +- plugins/nexus-blobstore-s3/pom.xml | 1 - .../pages/user/Welcome/UsageMetrics.jsx | 6 +- .../pages/user/Welcome/UsageMetrics.test.jsx | 6 +- .../pages/user/Welcome/UsageMetricsAlert.jsx | 36 +++++++++--- .../user/Welcome/UsageMetricsAlert.test.jsx | 57 +++++++++++++++++-- .../Welcome/UsageMetricsAlert.testdata.js | 36 ++++++++++++ .../UsageMetricsWithCircuitBreaker.jsx | 20 +++---- .../components/pages/user/Welcome/Welcome.jsx | 7 +-- .../pages/user/Welcome/Welcome.test.jsx | 35 +++++++++++- .../constants/pages/user/WelcomeStrings.jsx | 45 +++++++++++---- .../NX/coreui/view/react/FooterContainer.js | 11 +++- .../onboarding/internal/InstanceStatus.java | 31 ++++++++++ .../internal/InstanceStatusTest.java | 47 +++++++++++++++ .../orient/OrientRawRestoreBlobStrategy.java | 4 +- .../nexus/content/raw/RawContentFacet.java | 7 ++- .../raw/internal/RawContentFacetImpl.java | 7 ++- .../OrientRawRestoreBlobStrategyTest.java | 4 +- pom.xml | 4 ++ revision.txt | 2 +- 22 files changed, 343 insertions(+), 56 deletions(-) create mode 100644 components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/ContentUsage.java rename components/nexus-common/src/main/java/org/sonatype/nexus/common/{app => analytics}/UsageMetrics.java (95%) create mode 100644 plugins/nexus-onboarding-plugin/src/main/java/org/sonatype/nexus/onboarding/internal/InstanceStatus.java create mode 100644 plugins/nexus-onboarding-plugin/src/test/java/org/sonatype/nexus/onboarding/internal/InstanceStatusTest.java diff --git a/components/nexus-bootstrap/src/main/java/org/sonatype/nexus/bootstrap/osgi/ProStarterNexusEdition.java b/components/nexus-bootstrap/src/main/java/org/sonatype/nexus/bootstrap/osgi/ProStarterNexusEdition.java index beb10aa35d..609841f777 100644 --- a/components/nexus-bootstrap/src/main/java/org/sonatype/nexus/bootstrap/osgi/ProStarterNexusEdition.java +++ b/components/nexus-bootstrap/src/main/java/org/sonatype/nexus/bootstrap/osgi/ProStarterNexusEdition.java @@ -45,6 +45,7 @@ protected void doApply(final Properties properties, final Path workDirPath) { log.info("Loading Pro Starter Edition"); properties.setProperty(NEXUS_EDITION, getEdition().editionString); properties.setProperty(NEXUS_FEATURES, getEditionFeature().featureString); + properties.setProperty("nexus.analytics.enabled", Boolean.TRUE.toString()); createEditionMarker(workDirPath, getEdition()); } diff --git a/components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/ContentUsage.java b/components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/ContentUsage.java new file mode 100644 index 0000000000..9aa0a80201 --- /dev/null +++ b/components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/ContentUsage.java @@ -0,0 +1,30 @@ +/* + * Sonatype Nexus (TM) Open Source Version + * Copyright (c) 2008-present Sonatype, Inc. + * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions. + * + * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, + * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks + * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the + * Eclipse Foundation. All other trademarks are the property of their respective owners. + */ +package org.sonatype.nexus.common.analytics; + +import java.util.Map; + +public interface ContentUsage +{ + Map getMetrics(); + + boolean shouldBlock(); + + void recalculate(); + + void recalculate(boolean blockingStateEventCounterEnable); + + int getBlockingStateEventCounter(); + + void clearBlockingStateEventCounter(); +} diff --git a/components/nexus-common/src/main/java/org/sonatype/nexus/common/app/UsageMetrics.java b/components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/UsageMetrics.java similarity index 95% rename from components/nexus-common/src/main/java/org/sonatype/nexus/common/app/UsageMetrics.java rename to components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/UsageMetrics.java index 7d5bc16f90..254e069a94 100644 --- a/components/nexus-common/src/main/java/org/sonatype/nexus/common/app/UsageMetrics.java +++ b/components/nexus-common/src/main/java/org/sonatype/nexus/common/analytics/UsageMetrics.java @@ -10,7 +10,7 @@ * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the * Eclipse Foundation. All other trademarks are the property of their respective owners. */ -package org.sonatype.nexus.common.app; +package org.sonatype.nexus.common.analytics; import java.util.Map; diff --git a/plugins/nexus-blobstore-s3/pom.xml b/plugins/nexus-blobstore-s3/pom.xml index 56f6400173..af4b990389 100644 --- a/plugins/nexus-blobstore-s3/pom.xml +++ b/plugins/nexus-blobstore-s3/pom.xml @@ -166,7 +166,6 @@ aws-java-sdk-sts, aws-java-sdk-core, aws-java-sdk-kms, - ion-java, jmespath-java, aws-java-sdk-cloudwatch, aws-java-sdk-cloudwatchmetrics, diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.jsx index c8e1140a16..f0867a3320 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.jsx @@ -42,20 +42,18 @@ export default function UsageMetrics() { const isProEdition = ExtJS.isProEdition(); const isHa = ExtJS.state().getValue('nexus.datastore.clustered.enabled'); const isCircuitBreakerEnabled = ExtJS.state().getValue('nexus.circuitb.enabled'); - const isAdmin = ExtJS.state().getUser()?.administrator; - const showMetricsWithMeter = isCircuitBreakerEnabled && isAdmin; function retry() { send('RETRY'); }; - return !isHa &&
+ return !isHa &&
{() => <> {MENU.TEXT} - {showMetricsWithMeter ? : + {isCircuitBreakerEnabled ? : <> diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.test.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.test.jsx index b065bc3138..ccfba12250 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.test.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetrics.test.jsx @@ -361,7 +361,7 @@ describe('Usage Metrics', () => { let infoIcon = selectors.getCardInfoIcon(totalComponentsCard); await TestUtils.expectToSeeTooltipOnHover(infoIcon, - 'Sonatype Nexus Repository OSS performs best when your total component counts remain under the threshold.'); + 'Sonatype Nexus Repository OSS performs best when your total component counts remain under 100,000 components across all repositories in your instance.'); infoIcon = selectors.getCardInfoIcon(uniqueLoginsCard); await TestUtils.expectToSeeTooltipOnHover(infoIcon, @@ -369,7 +369,7 @@ describe('Usage Metrics', () => { infoIcon = selectors.getCardInfoIcon(reqsPerDayCard); await TestUtils.expectToSeeTooltipOnHover(infoIcon, - 'Sonatype Nexus Repository OSS performs best when requests per day remain under the threshold.'); + 'Sonatype Nexus Repository OSS performs best when requests per day remain under 20,000 requests per day to all repository endpoints across all repositories in your instance.'); }); it('renders tooltips when hovering on the info icon when PRO edition', async () => { @@ -386,7 +386,7 @@ describe('Usage Metrics', () => { infoIcon = selectors.getCardInfoIcon(reqsPerMinuteCard); await TestUtils.expectToSeeTooltipOnHover(infoIcon, - 'Measures requests per minute to your Sonatype Nexus Repository Pro instance.'); + 'Measures requests per minute to repository endpoints for all repositories in your Sonatype Nexus Repository Pro instance.'); infoIcon = selectors.getCardInfoIcon(reqsPerDayCard); await TestUtils.expectToSeeTooltipOnHover(infoIcon, diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.jsx index 515002bd5b..066e9f0e80 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.jsx @@ -11,7 +11,7 @@ * Eclipse Foundation. All other trademarks are the property of their respective owners. */ import React from 'react'; -import {NxButtonBar, NxErrorAlert, NxTextLink} from '@sonatype/react-shared-components'; +import {NxButtonBar, NxErrorAlert, NxTextLink, NxWarningAlert} from '@sonatype/react-shared-components'; import {ExtJS} from '@sonatype/nexus-ui-plugin'; import {isEmpty} from 'ramda'; @@ -22,8 +22,12 @@ import './UsageMetricsAlert.scss'; const { WELCOME: { USAGE: { + CIRCUIT_BREAKER: { + PERCENTAGE + }, ALERTS: { HARD_LIMITS, + WARNING_LIMITS, LEARN_ABOUT_PRO, REVIEW_YOUR_USAGE, UPGRADING_PRO, @@ -34,9 +38,9 @@ const COMPONENT_TOTAL_COUNT = 'component_total_count'; const REQUESTS_PER_DAY_HARD_LIMIT = 200_000; const COMPONENT_COUNT_HARD_LIMIT = 120_000; -const MessageContent = function({metricMessage}){ +const MessageContent = function({metricMessage, limit}){ return

- {metricMessage.PREFIX} + {metricMessage.PREFIX(limit)} {REVIEW_YOUR_USAGE.TEXT} {metricMessage.MID} {UPGRADING_PRO.TEXT} @@ -46,16 +50,25 @@ const MessageContent = function({metricMessage}){ const AlertContent = function({metric, content}) { return <> - {metric.metricName === PEAK_REQUESTS_PER_DAY && } - {metric.metricName === COMPONENT_TOTAL_COUNT && } + {metric.metricName === PEAK_REQUESTS_PER_DAY && } + {metric.metricName === COMPONENT_TOTAL_COUNT && } }; -export default function UsageMetricsAlert() { +export default function UsageMetricsAlert({onClose}) { const metrics = ExtJS.state().getValue('contentUsageEvaluationResult'); - const hardLimitMetrics = metrics.filter( - m => (m.metricName === PEAK_REQUESTS_PER_DAY && m.metricValue >= REQUESTS_PER_DAY_HARD_LIMIT) || - (m.metricName === COMPONENT_TOTAL_COUNT && m.metricValue >= COMPONENT_COUNT_HARD_LIMIT)); + const hardLimitMetrics = metrics.filter(m => + (m.metricName === PEAK_REQUESTS_PER_DAY && m.metricValue >= REQUESTS_PER_DAY_HARD_LIMIT) || + (m.metricName === COMPONENT_TOTAL_COUNT && m.metricValue >= COMPONENT_COUNT_HARD_LIMIT) + ); + const warningLimitMetrics = metrics.filter(m => + (m.metricName === PEAK_REQUESTS_PER_DAY && m.metricValue >= REQUESTS_PER_DAY_HARD_LIMIT * PERCENTAGE) || + (m.metricName === COMPONENT_TOTAL_COUNT && m.metricValue >= COMPONENT_COUNT_HARD_LIMIT * PERCENTAGE) + ); + const showWarningAlert = isEmpty(hardLimitMetrics) && !isEmpty(warningLimitMetrics); + return <> {!isEmpty(hardLimitMetrics) &&

@@ -69,5 +82,10 @@ export default function UsageMetricsAlert() { } + {showWarningAlert && +
+ {warningLimitMetrics.map(m => )} +
+
} }; diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.test.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.test.jsx index e76cc24ab0..8d834ae516 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.test.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.test.jsx @@ -12,12 +12,13 @@ */ import React from 'react'; import {render, screen} from '@testing-library/react'; +import userEvent from "@testing-library/user-event"; import {when} from "jest-when"; import UsageMetricsAlert from './UsageMetricsAlert'; import TestUtils from '@sonatype/nexus-ui-plugin/src/frontend/src/interface/TestUtils'; import {ExtJS} from '@sonatype/nexus-ui-plugin'; -import {HARD_LIMIT_REACHED} from './UsageMetricsAlert.testdata'; +import {HARD_LIMIT_REACHED, WARNING_LIMIT_REACHED} from './UsageMetricsAlert.testdata'; jest.mock('@sonatype/nexus-ui-plugin', () => ({ ...jest.requireActual('@sonatype/nexus-ui-plugin'), @@ -31,7 +32,8 @@ jest.mock('@sonatype/nexus-ui-plugin', () => ({ const selectors = { ...TestUtils.selectors, getAlert: () => screen.getByRole('alert'), - getLearnAboutLink: () => screen.getByRole('link', {name: 'Learn about Pro'}) + getCloseButton: () => screen.getByRole('button', {name: 'Close'}), + getLinks: (t) => screen.getAllByRole('link', {name: t}) }; describe('Usage Metrics Alert', () => { @@ -53,10 +55,57 @@ describe('Usage Metrics Alert', () => { const requestsPerDayMessage = 'Users can not currently upload to this repository. This repository has hit ' + 'the maximum of 200,000 peak requests in the past 30 days. Review your usage and consider upgrading to Pro ' + 'for unlimited usage.'; - const link = selectors.getLearnAboutLink(); + + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent(componentCountMessage); + expect(alert).toHaveTextContent(requestsPerDayMessage); + expectLinksToBeRendered('Learn about Pro', 'Review your usage', 'upgrading to Pro'); + }); + + it('renders warning limit alert when warning limit value reached', async () => { + await renderView(WARNING_LIMIT_REACHED); + + const alert = selectors.getAlert(); + const componentCountMessage = 'This repository is approaching the maximum of 120,000 components. ' + + 'Users will not be able to upload to this repository after reaching this limit. ' + + 'Review your usage and consider removing unused components or upgrading to Pro for unlimited usage.'; + const requestsPerDayMessage = 'This repository is approaching the maximum of 200,000 peak requests in the past 30 days. ' + + 'Users will not be able to upload to this repository after reaching this limit. ' + + 'Review your usage and consider upgrading to Pro for unlimited usage.'; + expect(alert).toBeInTheDocument(); expect(alert).toHaveTextContent(componentCountMessage); expect(alert).toHaveTextContent(requestsPerDayMessage); - expect(link).toBeInTheDocument(); + expectLinksToBeRendered('Review your usage', 'upgrading to Pro'); + }); + + it("tests the close button in the alert", async () => { + const onClose = jest.fn(); + await renderView(WARNING_LIMIT_REACHED, onClose); + + const alert = selectors.getAlert(); + + expect(alert).toBeInTheDocument(); + userEvent.click(selectors.getCloseButton()); + expect(onClose).toBeCalled(); }); }); + +function expectLinksToBeRendered(...links) { + + for (let l of links) { + const links = selectors.getLinks(l); + if (l === 'Learn about Pro') { + expect(links.length).toBe(1); + expect(links[0]).toHaveAttribute('href', 'https://links.sonatype.com/products/nxrm3/docs/learn-about-pro'); + } else if (l === 'Review your usage') { + expect(links.length).toBe(2); + expect(links[0]).toHaveAttribute('href', 'https://links.sonatype.com/products/nxrm3/docs/review-usage'); + expect(links[1]).toHaveAttribute('href', 'https://links.sonatype.com/products/nxrm3/docs/review-usage'); + } else { + expect(links.length).toBe(2); + expect(links[0]).toHaveAttribute('href', 'https://links.sonatype.com/products/nxrm3/docs/upgrade-to-pro'); + expect(links[1]).toHaveAttribute('href', 'https://links.sonatype.com/products/nxrm3/docs/upgrade-to-pro'); + } + } +} diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.testdata.js b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.testdata.js index 1f7137b629..184207190d 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.testdata.js +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsAlert.testdata.js @@ -49,3 +49,39 @@ export const HARD_LIMIT_REACHED = [ "limitLevel": "FREE_TIER" } ]; + +export const WARNING_LIMIT_REACHED = [ + { + "metricName": "peak_requests_per_day", + "metricValue": 150000, + "limits": [ + { + "limitName": "SOFT_LIMIT", + "limitValue": 20000 + } + ], + "limitLevel": "SOFT_LIMIT" + }, + { + "metricName": "component_total_count", + "metricValue": 90000, + "limits": [ + { + "limitName": "SOFT_LIMIT", + "limitValue": 100000 + } + ], + "limitLevel": "SOFT_LIMIT" + }, + { + "metricName": "successful_last_24h", + "metricValue": 26, + "limits": [ + { + "limitName": "SOFT_LIMIT", + "limitValue": 75 + } + ], + "limitLevel": "FREE_TIER" + } +]; diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsWithCircuitBreaker.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsWithCircuitBreaker.jsx index 2ab88046c9..0a1a894109 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsWithCircuitBreaker.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/UsageMetricsWithCircuitBreaker.jsx @@ -63,7 +63,7 @@ function Card({card, usage}) { }; -function CardWithThreshold({card, usage, link, tooltip}) { +function CardWithThreshold({card, usage, link, tooltip, edition}) { const {AGGREGATE_PERIOD_30_D, HIGHEST_RECORDED_COUNT, THRESHOLD, METRIC_NAME, SUB_TITLE, TITLE} = card; const cardData = usage.find(m => m.metricName === METRIC_NAME); const {aggregates, limits, metricValue} = cardData; @@ -84,7 +84,7 @@ function CardWithThreshold({card, usage, link, tooltip}) { {TITLE} - + @@ -120,7 +120,7 @@ function CardWithThreshold({card, usage, link, tooltip}) { }; -function CardWithHardLimitThreshold({card, usage, link, tooltip}) { +function CardWithHardLimitThreshold({card, usage, link, tooltip, edition}) { const {AGGREGATE_PERIOD_30_D, HIGHEST_RECORDED_COUNT, THRESHOLD, METRIC_NAME, SUB_TITLE, TITLE} = card; const cardData = usage.find(m => m.metricName === METRIC_NAME); const {aggregates, metricValue} = cardData; @@ -140,7 +140,7 @@ function CardWithHardLimitThreshold({card, usage, link, tooltip}) { {TITLE} - + @@ -224,21 +224,21 @@ export default function UsageMetricsWithCircuitBreaker() { } else if (isProEdition && !isPostgresql) { return <> - + - + } else if (isProStarterEdition) { return <> - + - + } else { return <> - + - + } }; diff --git a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/Welcome.jsx b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/Welcome.jsx index 5c79e203a9..86d120fbd4 100644 --- a/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/Welcome.jsx +++ b/plugins/nexus-coreui-plugin/src/frontend/src/components/pages/user/Welcome/Welcome.jsx @@ -12,7 +12,7 @@ */ import React, {useEffect, useRef, useState} from 'react'; import {useMachine} from '@xstate/react'; -import {ExtJS, toURIParams, getVersionMajorMinor, Permissions} from '@sonatype/nexus-ui-plugin'; +import {ExtJS, toURIParams, getVersionMajorMinor} from '@sonatype/nexus-ui-plugin'; import { NxButton, NxButtonBar, @@ -64,8 +64,7 @@ export default function Welcome() { usertype: getUserType(user), daysToExpiry: ExtJS.useLicense().daysToExpiry }, - canViewMetrics = ExtJS.checkPermission(Permissions.METRICS.READ), - hasUser = getUserType(user) !== 'anonymous'; + isAdmin = user?.administrator; function load() { send('LOAD'); @@ -136,7 +135,7 @@ export default function Welcome() { } - {canViewMetrics && hasUser && } + {isAdmin && } { state.context.data?.showOutreachIframe &&