From c4ec9866f663b429442c5f50407e04cd59e718df Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Wed, 12 Jun 2024 15:29:44 +0200 Subject: [PATCH 01/22] Include profile information in metrics GET method --- poem/Poem/api/internal_views/metrics.py | 7 +++++-- poem/Poem/api/tests/test_metrics.py | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/poem/Poem/api/internal_views/metrics.py b/poem/Poem/api/internal_views/metrics.py index 2074b8148..2aadce9c6 100644 --- a/poem/Poem/api/internal_views/metrics.py +++ b/poem/Poem/api/internal_views/metrics.py @@ -46,11 +46,12 @@ def get(self, request, name=None): if name: metrics = poem_models.Metric.objects.filter(name=name) if metrics.count() == 0: - raise NotFound(status=404, - detail='Metric not found') + raise NotFound(status=404, detail='Metric not found') else: metrics = poem_models.Metric.objects.all() + profiles4metrics = get_metrics_in_profiles(request.tenant) + results = [] for metric in metrics: if metric.probeversion: @@ -88,6 +89,8 @@ def get(self, request, name=None): name=metric.name, mtype=mt.mtype.name, tags=[tag.name for tag in mt.tags.all()], + profiles=profiles4metrics[metric.name] + if metric.name in profiles4metrics else [], probeversion=metric.probeversion if metric.probeversion else "", group=group, description=mt.description, diff --git a/poem/Poem/api/tests/test_metrics.py b/poem/Poem/api/tests/test_metrics.py index 9fef91a86..bd9843534 100644 --- a/poem/Poem/api/tests/test_metrics.py +++ b/poem/Poem/api/tests/test_metrics.py @@ -313,6 +313,11 @@ def mock_db(): content_type=ct ) +profiles4metrics = { + "argo.AMS-Check": ["ARGO_MON", "ARGO_MON_CRITICAL"], + "argo.AMSPublisher-Check": ["ARGO_MON_INTERNAL"] +} + class ListAllMetricsAPIViewTests(TenantTestCase): @factory.django.mute_signals(pre_save) @@ -424,8 +429,11 @@ def setUp(self): name="org.apel.APEL-Pub" ) - def test_get_metric_list(self): + @patch("Poem.api.internal_views.metrics.get_metrics_in_profiles") + def test_get_metric_list(self, mock_profiles4metrics): + mock_profiles4metrics.return_value = profiles4metrics request = self.factory.get(self.url) + request.tenant = self.tenant force_authenticate(request, user=self.user) response = self.view(request) self.assertEqual( @@ -436,6 +444,7 @@ def test_get_metric_list(self): 'name': 'argo.AMS-Check', 'mtype': 'Active', 'tags': ['test_tag1', 'test_tag2'], + "profiles": ["ARGO_MON", "ARGO_MON_CRITICAL"], 'probeversion': 'ams-probe (0.1.7)', 'group': 'EGI', 'description': 'Description of argo.AMS-Check', @@ -490,6 +499,7 @@ def test_get_metric_list(self): 'name': 'argo.AMSPublisher-Check', 'mtype': 'Active', 'tags': ['test_tag1'], + "profiles": ["ARGO_MON_INTERNAL"], 'probeversion': 'ams-publisher-probe (0.1.7)', 'group': 'EUDAT', 'description': '', @@ -538,6 +548,7 @@ def test_get_metric_list(self): 'name': 'org.apel.APEL-Pub', 'mtype': 'Passive', 'tags': [], + "profiles": [], 'probeversion': '', 'group': 'EGI', 'description': '', @@ -563,8 +574,11 @@ def test_get_metric_list(self): ] ) - def test_get_metric_by_name(self): + @patch("Poem.api.internal_views.metrics.get_metrics_in_profiles") + def test_get_metric_by_name(self, mock_profiles4metrics): + mock_profiles4metrics.return_value = profiles4metrics request = self.factory.get(self.url + 'argo.AMS-Check') + request.tenant = self.tenant force_authenticate(request, user=self.user) response = self.view(request, 'argo.AMS-Check') self.assertEqual( @@ -574,6 +588,7 @@ def test_get_metric_by_name(self): 'name': 'argo.AMS-Check', 'mtype': 'Active', 'tags': ['test_tag1', 'test_tag2'], + "profiles": ["ARGO_MON", "ARGO_MON_CRITICAL"], 'probeversion': 'ams-probe (0.1.7)', 'group': 'EGI', 'description': 'Description of argo.AMS-Check', From 81fd7e3049cf37bcf63e2eed2cdf0be7469864e2 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 13 Jun 2024 08:26:31 +0200 Subject: [PATCH 02/22] New message when deleting metrics --- poem/Poem/frontend/react/Metrics.js | 30 ++++++++++++++----- .../frontend/react/__tests__/Metrics.test.js | 5 ++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/poem/Poem/frontend/react/Metrics.js b/poem/Poem/frontend/react/Metrics.js index 6ace548c4..7be3234f1 100644 --- a/poem/Poem/frontend/react/Metrics.js +++ b/poem/Poem/frontend/react/Metrics.js @@ -860,6 +860,7 @@ export const MetricForm = defaultValues: { id: initValues.id ? initValues.id : "", name: initValues.name, + profiles: initValues.profiles, probeversion: initValues.probeversion, type: initValues.type, group: initValues.group ? initValues.group: "", @@ -968,6 +969,25 @@ export const MetricForm = toggleAreYouSure() } + function onDeleteHandle() { + let profiles = getValues("profiles") + let name = getValues("name") + let msg = "" + + if (resourcename === "metric" && !isHistory && profiles.length > 0) { + msg =
+

Metric { name } is part of profile(s): { profiles.join(", ") }

+

ARE YOU SURE you want to delete it?

+
+ } else + msg = `Are you sure you want to delete ${resourcename_beautify}?` + + setModalMsg(msg) + setModalTitle(`Delete ${resourcename_beautify}`) + setModalFlag('delete') + toggleAreYouSure() + } + return ( { - setModalMsg(`Are you sure you want to delete ${resourcename_beautify}?`) - setModalTitle(`Delete ${resourcename_beautify}`) - setModalFlag('delete') - toggleAreYouSure() - }} + onClick={() => onDeleteHandle()} > Delete @@ -1707,7 +1722,8 @@ export const MetricChange = (props) => { file_attributes: metric.files, file_parameters: metric.fileparameter, probe: probe, - tags: metric.tags + tags: metric.tags, + profiles: metric.profiles }} isTenantSchema={ true } groups={ groups } diff --git a/poem/Poem/frontend/react/__tests__/Metrics.test.js b/poem/Poem/frontend/react/__tests__/Metrics.test.js index 65f91871c..08768e7f1 100644 --- a/poem/Poem/frontend/react/__tests__/Metrics.test.js +++ b/poem/Poem/frontend/react/__tests__/Metrics.test.js @@ -15,6 +15,7 @@ const mockListOfMetrics = [ name: 'argo.AMS-Check', mtype: 'Active', tags: ['test_tag1', 'test_tag2'], + profiles: ["ARGO_MON", "TEST_PROFILE"], probeversion: 'ams-probe (0.1.12)', group: 'EGI', description: 'Description of argo.AMS-Check metric', @@ -44,6 +45,7 @@ const mockListOfMetrics = [ name: 'argo.AMS-Publisher', mtype: 'Active', tags: ['internal'], + profiles: ["ARGO_MON_INTERNAL"], probeversion: 'ams-publisher-probe (0.1.12)', group: 'EGI', description: '', @@ -74,6 +76,7 @@ const mockListOfMetrics = [ name: 'org.apel.APEL-Pub', mtype: 'Passive', tags: [], + profiles: [], probeversion: '', group: 'ARGOTEST', description: '', @@ -97,6 +100,7 @@ const mockMetric = { name: 'argo.AMS-Check', mtype: 'Active', tags: ['test_tag1', 'test_tag2'], + profiles: ["ARGO_MON", "TEST_PROFILE"], probeversion: 'ams-probe (0.1.12)', group: 'EGI', description: 'Description of argo.AMS-Check metric', @@ -127,6 +131,7 @@ const mockPassiveMetric = { name: 'org.apel.APEL-Pub', mtype: 'Passive', tags: [], + profiles: [], probeversion: '', group: 'ARGOTEST', description: '', From 1cc7a015aed7abf7dede4be152d3e43d88001a88 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 13 Jun 2024 10:45:27 +0200 Subject: [PATCH 03/22] Move metric templates table to a separate function --- poem/Poem/frontend/react/MetricTags.js | 365 +++++++++++++------------ 1 file changed, 195 insertions(+), 170 deletions(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index ad586fd93..c69b9f754 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -1,4 +1,4 @@ -import React, { useState } from "react" +import React, { useContext, useState } from "react" import { useMutation, useQuery, useQueryClient } from "react-query" import { Link, useLocation, useParams, useNavigate } from "react-router-dom" import { fetchMetricTags, fetchMetricTemplates, fetchUserDetails } from "./QueryFunctions" @@ -31,7 +31,7 @@ import { } from "reactstrap" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faSearch,faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { Controller, useForm, useWatch } from "react-hook-form" +import { Controller, FormProvider, useForm, useFormContext, useWatch } from "react-hook-form" import { ErrorMessage } from '@hookform/error-message' import { yupResolver } from '@hookform/resolvers/yup' import * as Yup from "yup" @@ -46,6 +46,9 @@ const validationSchema = Yup.object().shape({ }) +const MetricTagsContext = React.createContext() + + export const MetricTagsList = (props) => { const location = useLocation(); const publicView = props.publicView @@ -120,6 +123,125 @@ export const MetricTagsList = (props) => { } +const MetricsList = () => { + const context = useContext(MetricTagsContext) + + const { control, getValues, setValue } = useFormContext() + + return ( + + + + + + { + !context.publicView && + } + + + + + + + + { + context.metrics4tag.filter( + filteredRow => filteredRow.toLowerCase().includes(context.searchItem.toLowerCase()) + ).map((item, index) => + + + + + { + !context.publicView && + + } + + + ) + } + +
#Metric templateActions
+ + + + + } + /> +
+ { index + 1 } + + { + context.publicView ? + item + : + + { + let tmpMetrics = getValues("metrics4tag") + tmpMetrics[index] = e.value + setValue("metrics4tag", tmpMetrics) + } } + options={ + context.allMetrics.map( + met => met.name + ).filter( + met => !context.metrics4tag.includes(met) + ).map( + option => new Object({ label: option, value: option }) + )} + value={ { label: item, value: item } } + /> + } + /> + } + + + +
+ ) +} + + const MetricTagsForm = ({ name=undefined, tag=undefined, @@ -142,7 +264,7 @@ const MetricTagsForm = ({ const addMutation = useMutation(async (values) => await backend.addObject('/api/v2/internal/metrictags/', values)); const deleteMutation = useMutation(async () => await backend.deleteObject(`/api/v2/internal/metrictags/${name}`)) - const { control, getValues, setValue, handleSubmit, formState: { errors } } = useForm({ + const methods = useForm({ defaultValues: { id: `${tag ? tag.id : ""}`, name: `${tag ? tag.name : ""}`, @@ -153,6 +275,8 @@ const MetricTagsForm = ({ resolver: yupResolver(validationSchema) }) + const { control } = methods + const searchItem = useWatch({ control, name: "searchItem" }) const metrics4tag = useWatch({ control, name: "metrics4tag" }) @@ -171,7 +295,7 @@ const MetricTagsForm = ({ } const doChange = () => { - let formValues = getValues() + let formValues = methods.getValues() const sendValues = new Object({ name: formValues.name, @@ -279,178 +403,79 @@ const MetricTagsForm = ({ }} toggle={toggleAreYouSure} > -
- - - - - Name - - - } - /> - - - { message } - - } - /> - - - - - - - - - - - - { - !publicView && - } - - - - - - + + Name - } /> - - - { - metrics4tag.filter( - filteredRow => filteredRow.toLowerCase().includes(searchItem.toLowerCase()) - ).map((item, index) => - - - - - { - !publicView && - - } - - - ) - } - -
#Metric templateActions
- - + + + + +
- { index + 1 } - - { - publicView ? - item - : - - { - let tmpMetrics = getValues("metrics4tag") - tmpMetrics[index] = e.value - setValue("metrics4tag", tmpMetrics) - } } - options={ - allMetrics.map( - met => met.name - ).filter( - met => !metrics4tag.includes(met) - ).map( - option => new Object({ label: option, value: option }) - )} - value={ { label: item, value: item } } - /> - } - /> - } - - - -
-
- { - !publicView && -
- { - !addview ? - - : -
- } - -
- } -
+ + + { message } + + } + /> + + + + + + + + + + + { + !publicView && +
+ { + !addview ? + + : +
+ } + +
+ } + +
) } From 6791860d0bec2187d01be6dda3a83087a946a6ac Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 13 Jun 2024 13:10:03 +0200 Subject: [PATCH 04/22] Fix changing metrics when using filtered view --- poem/Poem/frontend/react/MetricTags.js | 3 +- .../react/__tests__/MetricTags.test.js | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index c69b9f754..b49c48cef 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -184,7 +184,8 @@ const MetricsList = () => { isClearable={ false } onChange={ (e) => { let tmpMetrics = getValues("metrics4tag") - tmpMetrics[index] = e.value + let origIndex = tmpMetrics.findIndex(e => e == item) + tmpMetrics[origIndex] = e.value setValue("metrics4tag", tmpMetrics) } } options={ diff --git a/poem/Poem/frontend/react/__tests__/MetricTags.test.js b/poem/Poem/frontend/react/__tests__/MetricTags.test.js index 10951d4cd..c48de1cf7 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTags.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTags.test.js @@ -561,6 +561,49 @@ describe("Test metric tags changeview", () => { ) }) + test("Test change metric tags name in filtered view", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: "connect" } }) + + fireEvent.change(screen.getByTestId("name"), { target: { value: "test_tag" } }) + + await waitFor(() => { + expect(screen.getByTestId("form")).toHaveFormValues({name: "test_tag"}) + }) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "test_tag", metrics: ["generic.certificate.validity", "generic.http.connect", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + test("Test error changing metric tag with error message", async () => { mockChangeObject.mockImplementationOnce(() => { throw Error("400 BAD REQUEST: Metric tag with this name already exists.") @@ -690,6 +733,45 @@ describe("Test metric tags changeview", () => { ) }) + test("Test change metrics for metric tag in filtered view", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: "connect" } }) + + await selectEvent.select(screen.getByText("generic.http.connect"), "argo.AMS-Check") + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "harmonized", metrics: ["generic.certificate.validity", "argo.AMS-Check", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + test("Test display warning messages", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ From 082023f277f00c665bbf2bc18756e6df5a70a192 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 13 Jun 2024 13:45:57 +0200 Subject: [PATCH 05/22] Fix deleting of metrics in filtered view --- poem/Poem/frontend/react/MetricTags.js | 3 +- .../react/__tests__/MetricTags.test.js | 80 ++++++++++++++++++- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index b49c48cef..6af7ad63f 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -211,7 +211,8 @@ const MetricsList = () => { data-testid={`remove-${index}`} onClick={() => { let tmpMetrics = context.metrics4tag - tmpMetrics.splice(index, 1) + let origIndex = tmpMetrics.findIndex(e => e == item) + tmpMetrics.splice(origIndex, 1) if (tmpMetrics.length === 0) tmpMetrics = [""] setValue("metrics4tag", tmpMetrics) diff --git a/poem/Poem/frontend/react/__tests__/MetricTags.test.js b/poem/Poem/frontend/react/__tests__/MetricTags.test.js index c48de1cf7..2a8f82e67 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTags.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTags.test.js @@ -699,8 +699,6 @@ describe("Test metric tags changeview", () => { await selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") - fireEvent.click(screen.getByTestId("remove-1")) - fireEvent.click(screen.getByTestId("insert-0")) const table = within(screen.getByRole("table")) @@ -718,7 +716,7 @@ describe("Test metric tags changeview", () => { await waitFor(() => { expect(mockChangeObject).toHaveBeenCalledWith( "/api/v2/internal/metrictags/", - { id: "4", name: "harmonized", metrics: ["argo.AMS-Check", "argo.AMSPublisher-Check", "generic.tcp.connect"] } + { id: "4", name: "harmonized", metrics: ["argo.AMS-Check", "argo.AMSPublisher-Check", "generic.http.connect", "generic.tcp.connect"] } ) }) @@ -772,6 +770,82 @@ describe("Test metric tags changeview", () => { ) }) + test("Test delete metrics from metric tag", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByTestId("remove-1")) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "harmonized", metrics: ["generic.certificate.validity", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + + test("Test delete metrics from metric tag if filtered view", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: "connect" } }) + + fireEvent.click(screen.getByTestId("remove-0")) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "harmonized", metrics: ["generic.certificate.validity", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + test("Test display warning messages", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ From 2bc9b2b73b1cd2cec175eda54e2bdf0c3a1ec796 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Fri, 14 Jun 2024 12:59:07 +0200 Subject: [PATCH 06/22] Change format of metric tags response --- .../api/internal_views/metrictemplates.py | 22 ++++-- poem/Poem/api/tests/test_metrictemplates.py | 72 ++++++++++++++----- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/poem/Poem/api/internal_views/metrictemplates.py b/poem/Poem/api/internal_views/metrictemplates.py index 41034fcb7..83c0f19cf 100644 --- a/poem/Poem/api/internal_views/metrictemplates.py +++ b/poem/Poem/api/internal_views/metrictemplates.py @@ -758,7 +758,10 @@ def get(self, request, name=None): return Response({ "id": tag.id, "name": tag.name, - "metrics": sorted([metric.name for metric in metrics]) + "metrics": sorted( + [{"name": metric.name} for metric in metrics], + key=lambda m: m["name"] + ) }) except admin_models.MetricTags.DoesNotExist: @@ -777,7 +780,10 @@ def get(self, request, name=None): data.append({ "id": tag.id, "name": tag.name, - "metrics": sorted([metric.name for metric in metrics]) + "metrics": sorted( + [{"name": metric.name} for metric in metrics], + key=lambda m: m["name"] + ) }) return Response(data) @@ -831,14 +837,16 @@ def post(self, request): if len(missing_metrics) > 0: if len(missing_metrics) == 1: - warn_msg = \ - f"{warn_msg}Metric {list(missing_metrics)[0]} " \ + warn_msg = ( + f"{warn_msg}Metric {list(missing_metrics)[0]} " f"does not exist." + ) else: - warn_msg = "{}Metrics {} do not exist.".format( - warn_msg, - ", ".join(sorted(list(missing_metrics))) + warn_msg = ( + f"{warn_msg}Metrics " + f"{', '.join(sorted(list(missing_metrics)))} " + f"do not exist." ) if warn_msg: diff --git a/poem/Poem/api/tests/test_metrictemplates.py b/poem/Poem/api/tests/test_metrictemplates.py index d6a046773..74ae8e4d4 100644 --- a/poem/Poem/api/tests/test_metrictemplates.py +++ b/poem/Poem/api/tests/test_metrictemplates.py @@ -10022,17 +10022,25 @@ def test_get_metric_tags_admin_superuser(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [{ + "name": "argo.AMS-Check" + }] }, { "id": self.tag3.id, "name": "test_tag1", - "metrics": ["argo.AMS-Check", "argo.EGI-Connectors-Check"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "argo.EGI-Connectors-Check"} + ] }, { "id": self.tag4.id, "name": "test_tag2", - "metrics": ["argo.AMS-Check", "org.apel.APEL-Pub"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "org.apel.APEL-Pub"} + ] } ] ) @@ -10052,17 +10060,25 @@ def test_get_metric_tags_admin_regular_user(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] }, { "id": self.tag3.id, "name": "test_tag1", - "metrics": ["argo.AMS-Check", "argo.EGI-Connectors-Check"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "argo.EGI-Connectors-Check"} + ] }, { "id": self.tag4.id, "name": "test_tag2", - "metrics": ["argo.AMS-Check", "org.apel.APEL-Pub"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "org.apel.APEL-Pub"} + ] } ] ) @@ -10082,17 +10098,25 @@ def test_get_metric_tags_tenant_superuser(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] }, { "id": self.tag3.id, "name": "test_tag1", - "metrics": ["argo.AMS-Check", "argo.EGI-Connectors-Check"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "argo.EGI-Connectors-Check"} + ] }, { "id": self.tag4.id, "name": "test_tag2", - "metrics": ["argo.AMS-Check", "org.apel.APEL-Pub"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "org.apel.APEL-Pub"} + ] } ] ) @@ -10112,17 +10136,25 @@ def test_get_metric_tags_tenant_regular_user(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] }, { "id": self.tag3.id, "name": "test_tag1", - "metrics": ["argo.AMS-Check", "argo.EGI-Connectors-Check"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "argo.EGI-Connectors-Check"} + ] }, { "id": self.tag4.id, "name": "test_tag2", - "metrics": ["argo.AMS-Check", "org.apel.APEL-Pub"] + "metrics": [ + {"name": "argo.AMS-Check"}, + {"name": "org.apel.APEL-Pub"} + ] } ] ) @@ -10141,7 +10173,9 @@ def test_get_metric_tag_by_name_admin_superuser(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] } ) @@ -10154,7 +10188,9 @@ def test_get_metric_tag_by_name_admin_regular_user(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] } ) @@ -10167,7 +10203,9 @@ def test_get_metric_tag_by_name_tenant_superuser(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] } ) @@ -10180,7 +10218,9 @@ def test_get_metric_tag_by_name_tenant_regular_user(self): { "id": self.tag1.id, "name": "internal", - "metrics": ["argo.AMS-Check"] + "metrics": [ + {"name": "argo.AMS-Check"} + ] } ) From ae9ea40873c95bd50df976294772f37b5cf2f78e Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Mon, 17 Jun 2024 14:01:26 +0200 Subject: [PATCH 07/22] Fix all problems with adding and deleting metrics in the UI --- poem/Poem/frontend/react/MetricTags.js | 89 +++++--- .../react/__tests__/MetricTags.test.js | 194 +++++++++++++++--- 2 files changed, 227 insertions(+), 56 deletions(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index 6af7ad63f..5f85a7ad0 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react" +import React, { useContext, useEffect, useState } from "react" import { useMutation, useQuery, useQueryClient } from "react-query" import { Link, useLocation, useParams, useNavigate } from "react-router-dom" import { fetchMetricTags, fetchMetricTemplates, fetchUserDetails } from "./QueryFunctions" @@ -31,7 +31,7 @@ import { } from "reactstrap" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faSearch,faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { Controller, FormProvider, useForm, useFormContext, useWatch } from "react-hook-form" +import { Controller, FormProvider, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form" import { ErrorMessage } from '@hookform/error-message' import { yupResolver } from '@hookform/resolvers/yup' import * as Yup from "yup" @@ -83,7 +83,7 @@ export const MetricTagsList = (props) => { : row.value.map((metric, i) => - { metric } + { metric.name } ) } @@ -126,7 +126,28 @@ export const MetricTagsList = (props) => { const MetricsList = () => { const context = useContext(MetricTagsContext) - const { control, getValues, setValue } = useFormContext() + const { control, getValues, setValue, resetField } = useFormContext() + + const { fields, insert, remove } = useFieldArray({ control, name: "view_metrics4tag" }) + + const onRemove = (index) => { + let tmp_metrics4tag = [ ...context.metrics4tag ] + let origIndex = tmp_metrics4tag.findIndex(e => e.name == getValues(`view_metrics4tag.${index}.name`)) + tmp_metrics4tag.splice(origIndex, 1) + resetField("metrics4tag") + setValue("metrics4tag", tmp_metrics4tag) + remove(index) + } + + const onInsert = (index) => { + let tmp_metrics4tag = [ ...context.metrics4tag ] + let origIndex = tmp_metrics4tag.findIndex(e => e.name == getValues(`view_metrics4tag.${index}.name`)) + let new_element = { name: "" } + tmp_metrics4tag.splice(origIndex + 1, 0, new_element) + resetField("metrics4tag") + setValue("metrics4tag", tmp_metrics4tag) + insert(index + 1, new_element) + } return ( @@ -160,9 +181,7 @@ const MetricsList = () => { { - context.metrics4tag.filter( - filteredRow => filteredRow.toLowerCase().includes(context.searchItem.toLowerCase()) - ).map((item, index) => + fields.map((item, index) => - - - + { + publicView ? + <> + + + + : + <> + + + + + } @@ -217,7 +230,9 @@ const PortsList = ({ data }) => { } /> - + { + !publicView && + } { fieldsView.map((entry, index) => @@ -283,32 +298,37 @@ const PortsList = ({ data }) => { { entry.value } } - + { + !publicView && + <> + + + } ) } @@ -323,12 +343,14 @@ const PortsList = ({ data }) => { } -export const DefaultPortsList = () => { +export const DefaultPortsList = (props) => { + const publicView = props.publicView + const backend = new Backend(); const { data: defaultPorts, error, status } = useQuery( - "defaultports", async () => { - return await backend.fetchData("/api/v2/internal/default_ports") + `${publicView ? "public_" : ""}defaultports`, async () => { + return await backend.fetchData(`/api/v2/internal/${publicView ? "public_" : ""}default_ports`) } ) @@ -337,7 +359,7 @@ export const DefaultPortsList = () => { Save + !publicView && } /> ) @@ -347,7 +369,10 @@ export const DefaultPortsList = () => { else if (defaultPorts) { return ( - + ) } else diff --git a/poem/Poem/frontend/react/__tests__/DefaultPorts.test.js b/poem/Poem/frontend/react/__tests__/DefaultPorts.test.js index 26d820115..bea2407d8 100644 --- a/poem/Poem/frontend/react/__tests__/DefaultPorts.test.js +++ b/poem/Poem/frontend/react/__tests__/DefaultPorts.test.js @@ -61,18 +61,30 @@ const mockDefaultPorts = [ ] -function renderView() { - const route = "/ui/administration/default_ports" - - return { - ...render( - - - - - - ) - } +function renderView(publicView=false) { + const route = `/ui/${publicView? "public_" : "administration/"}default_ports` + + if (publicView) + return { + ...render( + + + + + + ) + } + + else + return { + ...render( + + + + + + ) + } } @@ -119,6 +131,34 @@ describe("Test default ports list", () => { expect(table.getAllByTestId(/insert-/i)).toHaveLength(4) }) + test("Test that public page renders properly", async () => { + renderView(true); + + await waitFor(() => { + expect(screen.getAllByRole("columnheader")).toHaveLength(3) + }) + + expect(screen.getByRole("heading", { name: /port/i }).textContent).toBe("Default ports"); + + const table = within(screen.getByRole("table")) + expect(table.getByRole("columnheader", { name: "#" })).toBeInTheDocument() + expect(table.getByRole("columnheader", { name: "Port name" })).toBeInTheDocument() + expect(table.getByRole("columnheader", { name: "Port value" })).toBeInTheDocument() + + const rows = table.getAllByRole("row") + expect(rows).toHaveLength(6) + expect(screen.getAllByPlaceholderText(/search/i)).toHaveLength(2) + expect(rows[0].textContent).toBe("#Port namePort value") + // row 1 is the one with search fields + expect(rows[2].textContent).toBe("1BDII_PORT2170") + expect(rows[3].textContent).toBe("2GRAM_PORT2119") + expect(rows[4].textContent).toBe("3MYPROXY_PORT7512") + expect(rows[5].textContent).toBe("4SITE_BDII_PORT2170") + + expect(table.queryAllByTestId(/remove-/i)).toHaveLength(0) + expect(table.queryAllByTestId(/insert-/i)).toHaveLength(0) + }) + test("Test filter page by port name", async () => { renderView(); @@ -144,6 +184,31 @@ describe("Test default ports list", () => { expect(table.getAllByTestId(/insert-/i)).toHaveLength(2) }) + test("Test filter page by port name if public view", async () => { + renderView(true); + + await waitFor(() => { + expect(screen.getAllByRole("columnheader")).toHaveLength(3) + }) + + expect(screen.getByRole("heading", { name: /port/i }).textContent).toBe("Default ports"); + + fireEvent.change(screen.getAllByPlaceholderText(/search/i)[0], { target: { value: "BDII" } }) + + const table = within(screen.getByRole("table")) + + const rows = table.getAllByRole("row") + expect(rows).toHaveLength(4) + expect(screen.getAllByPlaceholderText(/search/i)).toHaveLength(2) + expect(rows[0].textContent).toBe("#Port namePort value") + // row 1 is the one with search fields + expect(rows[2].textContent).toBe("1BDII_PORT2170") + expect(rows[3].textContent).toBe("2SITE_BDII_PORT2170") + + expect(table.queryAllByTestId(/remove-/i)).toHaveLength(0) + expect(table.queryAllByTestId(/insert-/i)).toHaveLength(0) + }) + test("Test filter page by port value", async () => { renderView(); @@ -168,6 +233,30 @@ describe("Test default ports list", () => { expect(table.getAllByTestId(/insert-/i)).toHaveLength(1) }) + test("Test filter page by port value if public view", async () => { + renderView(true); + + await waitFor(() => { + expect(screen.getAllByRole("columnheader")).toHaveLength(3) + }) + + expect(screen.getByRole("heading", { name: /port/i }).textContent).toBe("Default ports"); + + fireEvent.change(screen.getAllByPlaceholderText(/search/i)[1], { target: { value: "75" } }) + + const table = within(screen.getByRole("table")) + + const rows = table.getAllByRole("row") + expect(rows).toHaveLength(3) + expect(screen.getAllByPlaceholderText(/search/i)).toHaveLength(2) + expect(rows[0].textContent).toBe("#Port namePort value") + // row 1 is the one with search fields + expect(rows[2].textContent).toBe("1MYPROXY_PORT7512") + + expect(table.queryAllByTestId(/remove-/i)).toHaveLength(0) + expect(table.queryAllByTestId(/insert-/i)).toHaveLength(0) + }) + test("Test adding new port", async () => { renderView(); From 7a96b63f86b6338b45ed9823476e2f1f629c57a5 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 13:13:46 +0200 Subject: [PATCH 14/22] Add public default ports links to public home page --- poem/Poem/frontend/react/Home.js | 9 +++++++++ poem/Poem/frontend/react/UIElements.js | 1 + 2 files changed, 10 insertions(+) diff --git a/poem/Poem/frontend/react/Home.js b/poem/Poem/frontend/react/Home.js index 8818df9d2..50ce658c2 100644 --- a/poem/Poem/frontend/react/Home.js +++ b/poem/Poem/frontend/react/Home.js @@ -51,6 +51,9 @@ export const PublicHome = (props) => {
Metric templates
+
+ Default ports +
@@ -145,6 +148,9 @@ export const PublicHome = (props) => {
Metric templates
+
+ Default ports +
@@ -168,6 +174,9 @@ export const PublicHome = (props) => {
Metric templates
+
+ Default ports +
diff --git a/poem/Poem/frontend/react/UIElements.js b/poem/Poem/frontend/react/UIElements.js index 9a3e6b706..173d64043 100644 --- a/poem/Poem/frontend/react/UIElements.js +++ b/poem/Poem/frontend/react/UIElements.js @@ -123,6 +123,7 @@ link_title.set('users', 'Users'); link_title.set('yumrepos', 'YUM repos'); link_title.set("metricoverrides", "Metric configuration overrides") link_title.set("default_ports", "Default ports") +link_title.set("public_default_ports", "Public default ports") link_title.set("probecandidates", "Probe candidates") From 61f8e408059eb0fc99bed3e84f656ce151e96fdf Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 15:38:02 +0200 Subject: [PATCH 15/22] JSON import/export button for metric overrides --- poem/Poem/frontend/react/MetricOverrides.js | 55 +++- .../react/__tests__/MetricOverrides.test.js | 303 ++++++++++++++++++ 2 files changed, 357 insertions(+), 1 deletion(-) diff --git a/poem/Poem/frontend/react/MetricOverrides.js b/poem/Poem/frontend/react/MetricOverrides.js index 119ebe29b..b3cb2205f 100644 --- a/poem/Poem/frontend/react/MetricOverrides.js +++ b/poem/Poem/frontend/react/MetricOverrides.js @@ -12,7 +12,11 @@ import { } from "./UIElements" import { Button, + ButtonDropdown, Col, + DropdownItem, + DropdownMenu, + DropdownToggle, Form, FormGroup, FormFeedback, @@ -39,6 +43,7 @@ import { InputPlaceholder, ListViewPlaceholder } from "./Placeholders" +import { downloadJSON } from './FileDownload'; const hostnameRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])([_][A-Za-z0-9.\-_]*)*$/ @@ -206,7 +211,7 @@ const MetricOverrideForm = ({ }) => { const backend = new Backend() const queryClient = useQueryClient() - const navigate = useNavigate() ///// + const navigate = useNavigate() const addMutation = useMutation(async (values) => await backend.addObject("/api/v2/internal/metricconfiguration/", values)) const changeMutation = useMutation(async (values) => await backend.changeObject("/api/v2/internal/metricconfiguration/", values)) @@ -216,6 +221,8 @@ const MetricOverrideForm = ({ const [modalMsg, setModalMsg] = useState(undefined); const [modalTitle, setModalTitle] = useState(undefined); const [modalFlag, setModalFlag] = useState(undefined); + const [dropdownOpen, setDropdownOpen] = useState(false); + const hiddenFileInput = React.useRef(null); const { control, handleSubmit, setValue, getValues, trigger, formState: { errors } } = useForm({ defaultValues: { @@ -352,6 +359,19 @@ const MetricOverrideForm = ({ }) } + const handleFileRead = (e) => { + let jsonData = JSON.parse(e.target.result); + setValue('globalAttributes', jsonData.global_attributes); + setValue('hostAttributes', jsonData.host_attributes); + setValue('metricParameters', jsonData.metric_parameters); + } + + const handleFileChosen = (file) => { + var reader = new FileReader(); + reader.onload = handleFileRead; + reader.readAsText(file); + } + return ( setDropdownOpen(!dropdownOpen) }> + JSON + + { + let valueSave = JSON.parse(JSON.stringify(getValues())); + const jsonContent = { + global_attributes: valueSave.globalAttributes, + host_attributes: valueSave.hostAttributes, + metric_parameters: valueSave.metricParameters + } + let filename = `${name}.json` + downloadJSON(jsonContent, filename) + }} + > + Export + + { hiddenFileInput.current.click() } } + > + Import + + + { handleFileChosen(e.target.files[0]) }} + style={{ display: 'none' }} + /> + + } >
diff --git a/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js b/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js index a871b57fc..d7552e045 100644 --- a/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js @@ -6,6 +6,7 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { Backend } from "../DataManager" import { MetricOverrideChange, MetricOverrideList } from "../MetricOverrides" import { NotificationManager } from "react-notifications" +import useEvent from '@testing-library/user-event'; jest.mock("../DataManager", () => { @@ -1732,6 +1733,7 @@ describe("Tests for metric configuration overrides changeview", () => { expect(screen.queryByRole('button', { name: /clone/i })).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /json/i })).toBeInTheDocument(); }) test("Test change global attributes", async () => { @@ -3141,6 +3143,307 @@ describe("Tests for metric configuration overrides changeview", () => { }) }) + test("Test import json successfully", async () => { + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /json/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /import/i })) + + const content = new Blob([JSON.stringify({ + global_attributes: [ + { + attribute: "ROBOT_CERT", + value: "/etc/sensu/certs/robotcert.pem" + }, + { + attribute: "ROBOT_KEY", + value: "/etc/sensu/certs/robotkey.pem" + } + ], + host_attributes: [ + { + hostname: "poem.devel.argo.grnet.gr", + attribute: "ARGO_TENANTS_TOKEN", + value: "$DEVEL_ARGO_TENANTS_TOKEN" + } + ], + metric_parameters: [ + { + hostname: "", + metric: "", + parameter: "", + value: "" + } + ] + })]) + + const file = new File([content], "test.json", { type: "application/json" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toBe(file) + }) + + expect(input.files.item(0)).toBe(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + expect(screen.getByTestId("metric-override-form")).toHaveFormValues({ + name: "local", + "globalAttributes.0.attribute": "ROBOT_CERT", + "globalAttributes.0.value": "/etc/sensu/certs/robotcert.pem", + "globalAttributes.1.attribute": "ROBOT_KEY", + "globalAttributes.1.value": "/etc/sensu/certs/robotkey.pem", + "hostAttributes.0.hostname": "poem.devel.argo.grnet.gr", + "hostAttributes.0.attribute": "ARGO_TENANTS_TOKEN", + "hostAttributes.0.value": "$DEVEL_ARGO_TENANTS_TOKEN", + "metricParameters.0.hostname": "", + "metricParameters.0.metric": "", + "metricParameters.0.parameter": "", + "metricParameters.0.value": "" + }) + + fireEvent.click(screen.getByRole("button", { name: /save/i })) + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /add/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metricconfiguration/", + { + id: "1", + name: "local", + global_attributes: [ + { + attribute: "ROBOT_CERT", + value: "/etc/sensu/certs/robotcert.pem" + }, + { + attribute: "ROBOT_KEY", + value: "/etc/sensu/certs/robotkey.pem" + } + ], + host_attributes: [ + { + hostname: "poem.devel.argo.grnet.gr", + attribute: "ARGO_TENANTS_TOKEN", + value: "$DEVEL_ARGO_TENANTS_TOKEN" + } + ], + metric_parameters: [ + { + hostname: "", + metric: "", + parameter: "", + value: "" + } + ] + } + ) + }) + }) + + test("Test export json successfully", async () => { + const helpers = require("../FileDownload") + jest.spyOn(helpers, "downloadJSON").mockReturnValueOnce(null) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /json/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /export/i })) + + const content = { + global_attributes: [ + { + attribute: "NAGIOS_ACTUAL_HOST_CERT", + value: "/etc/nagios/globus/hostcert.pem" + }, + { + attribute: "NAGIOS_ACTUAL_HOST_KEY", + value: "/etc/nagios/globus/hostkey.pem" + } + ], + host_attributes: [ + { + hostname: "mock.host.name", + attribute: "attr1", + value: "some-new-value" + } + ], + metric_parameters: [ + { + hostname: "eosccore.ui.argo.grnet.gr", + metric: "org.nagios.ARGOWeb-AR", + parameter: "-r", + value: "EOSC_Monitoring" + }, + { + hostname: "argo.eosc-portal.eu", + metric: "org.nagios.ARGOWeb-Status", + parameter: "-u", + value: "/eosc/report-status/Default/SERVICEGROUPS?accept=csv" + } + ] + } + + expect(helpers.downloadJSON).toHaveBeenCalledTimes(1) + expect(helpers.downloadJSON).toHaveBeenCalledWith(content, "local.json") + }) + + test("Test export json when form has been changed", async () => { + const helpers = require("../FileDownload") + jest.spyOn(helpers, "downloadJSON").mockReturnValueOnce(null) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("name"), { target: { value: "local_new" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.0.attribute"), { target: { value: "NAGIOS_REAL_HOST_CERT" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.0.value"), { target: { value: "/etc/nagios/globus/cert.pem" } }) + }) + + await waitFor(() => fireEvent.click(screen.getByTestId("globalAttributes.0.add"))) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.1.attribute"), { target: { value: "MOCK_ATTRIBUTE" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.1.value"), { target: { value: "mock-attribute-value" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.2.attribute"), { target: { value: "NAGIOS_REAL_HOST_KEY" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("globalAttributes.2.value"), { target: { value: "/etc/nagios/globus/cert.key" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("hostAttributes.0.hostname"), { target: { value: "host.foo.bar" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("hostAttributes.0.attribute"), { target: { value: "FOOBAR" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("hostAttributes.0.value"), { target: { value: "foo-bar" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.0.hostname"), { target: { value: "sensu.cro-ngi" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.0.metric"), { target: { value: "argo.AMSPublisher-Check" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.0.parameter"), { target: { value: "-q" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.0.value"), { target: { value: "w:metrics+g:published180 -c 10" } }) + }) + + await waitFor(() => fireEvent.click(screen.getByTestId("metricParameters.0.add"))) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.1.hostname"), { target: { value: "epic5.storage.surfsara.nl" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.1.metric"), { target: { value: "generic.tcp.connect" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.1.parameter"), { target: { value: "-p" } }) + }) + + await waitFor(() => { + fireEvent.change(screen.getByTestId("metricParameters.1.value"), { target: { value: "8004" } }) + }) + + fireEvent.click(screen.getByRole("button", { name: /json/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /export/i })) + + const content = { + global_attributes: [ + { + attribute: "NAGIOS_REAL_HOST_CERT", + value: "/etc/nagios/globus/cert.pem" + }, + { + attribute: "MOCK_ATTRIBUTE", + value: "mock-attribute-value" + }, + { + attribute: "NAGIOS_REAL_HOST_KEY", + value: "/etc/nagios/globus/cert.key" + } + ], + host_attributes: [ + { + hostname: "host.foo.bar", + attribute: "FOOBAR", + value: "foo-bar" + } + ], + metric_parameters: [ + { + hostname: "sensu.cro-ngi", + metric: "argo.AMSPublisher-Check", + parameter: "-q", + value: "w:metrics+g:published180 -c 10" + }, + { + hostname: "epic5.storage.surfsara.nl", + metric: "generic.tcp.connect", + parameter: "-p", + value: "8004" + }, + { + hostname: "argo.eosc-portal.eu", + metric: "org.nagios.ARGOWeb-Status", + parameter: "-u", + value: "/eosc/report-status/Default/SERVICEGROUPS?accept=csv" + } + ] + } + + expect(helpers.downloadJSON).toHaveBeenCalledTimes(1) + expect(helpers.downloadJSON).toHaveBeenCalledWith(content, "local.json") + }) + test("Test save metric override with only name", async () => { renderChangeView() From e2d483a1b9299ff346573d4a11fd4bb081defed9 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 15:45:32 +0200 Subject: [PATCH 16/22] Disable export on addview for metric overrides --- poem/Poem/frontend/react/MetricOverrides.js | 1 + .../react/__tests__/MetricOverrides.test.js | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/poem/Poem/frontend/react/MetricOverrides.js b/poem/Poem/frontend/react/MetricOverrides.js index b3cb2205f..624004613 100644 --- a/poem/Poem/frontend/react/MetricOverrides.js +++ b/poem/Poem/frontend/react/MetricOverrides.js @@ -408,6 +408,7 @@ const MetricOverrideForm = ({ let filename = `${name}.json` downloadJSON(jsonContent, filename) }} + disabled={ addview } > Export diff --git a/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js b/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js index d7552e045..52ef6e59a 100644 --- a/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricOverrides.test.js @@ -263,6 +263,7 @@ describe("Tests for metric configuration overrides addview", () => { expect(screen.queryByRole('button', { name: /history/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /clone/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /json/i })).toBeInTheDocument() }) test("Test add name", async () => { @@ -1308,6 +1309,78 @@ describe("Tests for metric configuration overrides addview", () => { }) }) + test("Test import json successfully", async () => { + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /json/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /import/i })) + + const content = new Blob([JSON.stringify({ + global_attributes: [ + { + attribute: "ROBOT_CERT", + value: "/etc/sensu/certs/robotcert.pem" + }, + { + attribute: "ROBOT_KEY", + value: "/etc/sensu/certs/robotkey.pem" + } + ], + host_attributes: [ + { + hostname: "poem.devel.argo.grnet.gr", + attribute: "ARGO_TENANTS_TOKEN", + value: "$DEVEL_ARGO_TENANTS_TOKEN" + } + ], + metric_parameters: [ + { + hostname: "", + metric: "", + parameter: "", + value: "" + } + ] + })]) + + const file = new File([content], "test.json", { type: "application/json" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toBe(file) + }) + + expect(input.files.item(0)).toBe(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + expect(screen.getByTestId("metric-override-form")).toHaveFormValues({ + name: "", + "globalAttributes.0.attribute": "ROBOT_CERT", + "globalAttributes.0.value": "/etc/sensu/certs/robotcert.pem", + "globalAttributes.1.attribute": "ROBOT_KEY", + "globalAttributes.1.value": "/etc/sensu/certs/robotkey.pem", + "hostAttributes.0.hostname": "poem.devel.argo.grnet.gr", + "hostAttributes.0.attribute": "ARGO_TENANTS_TOKEN", + "hostAttributes.0.value": "$DEVEL_ARGO_TENANTS_TOKEN", + "metricParameters.0.hostname": "", + "metricParameters.0.metric": "", + "metricParameters.0.parameter": "", + "metricParameters.0.value": "" + }) + }) + test("Test save metric override with just name", async () => { renderAddView() From 5958eab396e10f35d2bf385690677b941b010d10 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Wed, 19 Jun 2024 15:49:25 +0200 Subject: [PATCH 17/22] Import data option for metric tags --- poem/Poem/frontend/react/MetricTags.js | 81 +++++++- .../react/__tests__/MetricTags.test.js | 177 +++++++++++++++++- 2 files changed, 249 insertions(+), 9 deletions(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index 5f85a7ad0..f8c5f9503 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -5,6 +5,7 @@ import { fetchMetricTags, fetchMetricTemplates, fetchUserDetails } from "./Query import { BaseArgoTable, BaseArgoView, + CustomError, CustomReactSelect, DefaultColumnFilter, ErrorComponent, @@ -27,22 +28,39 @@ import { InputGroup, InputGroupText, Row, - Table + Table, + ButtonDropdown, + DropdownToggle, + DropdownMenu, + DropdownItem } from "reactstrap" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faSearch,faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'; import { Controller, FormProvider, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form" import { ErrorMessage } from '@hookform/error-message' import { yupResolver } from '@hookform/resolvers/yup' -import * as Yup from "yup" +import * as yup from "yup" import { ChangeViewPlaceholder, InputPlaceholder, ListViewPlaceholder } from "./Placeholders" +import PapaParse from 'papaparse'; -const validationSchema = Yup.object().shape({ - name: Yup.string().required("This field is required") + +const validationSchema = yup.object().shape({ + name: yup.string().required("This field is required"), + view_metrics4tag: yup.array() + .of(yup.object().shape({ + name: yup.string() + .test("predefined_metrics", "Must be one of predefined metrics", function (value) { + if (value && this.options.context.metrictemplates.indexOf(value) == -1) + return false + + else + return true + }) + })) }) @@ -126,7 +144,7 @@ export const MetricTagsList = (props) => { const MetricsList = () => { const context = useContext(MetricTagsContext) - const { control, getValues, setValue, resetField } = useFormContext() + const { control, getValues, setValue, resetField, trigger, formState: { errors } } = useFormContext() const { fields, insert, remove } = useFieldArray({ control, name: "view_metrics4tag" }) @@ -137,6 +155,7 @@ const MetricsList = () => { resetField("metrics4tag") setValue("metrics4tag", tmp_metrics4tag) remove(index) + trigger("view_metrics4tag") } const onInsert = (index) => { @@ -201,10 +220,12 @@ const MetricsList = () => { forwardedRef={ field.ref } id={ `metric-${index}` } isClearable={ false } + error={ errors?.view_metrics4tag?.[index]?.name } onChange={ (e) => { let origIndex = getValues("metrics4tag").findIndex(met => met.name == getValues(`view_metrics4tag.${index}.name`) ) setValue(`metrics4tag.${origIndex}.name`, e.value) setValue(`view_metrics4tag.${index}.name`, e.value) + trigger(`view_metrics4tag.${index}.name`) } } options={ context.allMetrics.map( @@ -219,6 +240,10 @@ const MetricsList = () => { } /> } + { + errors?.view_metrics4tag?.[index]?.name && + + } { !context.publicView && @@ -268,6 +293,8 @@ const MetricTagsForm = ({ const [modalFlag, setModalFlag] = useState(undefined); const [modalTitle, setModalTitle] = useState(undefined); const [modalMsg, setModalMsg] = useState(undefined); + const [dropdownOpen, setDropdownOpen] = useState(false); + const hiddenFileInput = React.useRef(null); const changeMutation = useMutation(async (values) => await backend.changeObject('/api/v2/internal/metrictags/', values)); const addMutation = useMutation(async (values) => await backend.addObject('/api/v2/internal/metrictags/', values)); @@ -284,7 +311,8 @@ const MetricTagsForm = ({ searchItem: "" }, mode: "all", - resolver: yupResolver(validationSchema) + resolver: yupResolver(validationSchema), + context: { metrictemplates: publicView ? new Array() : allMetrics.map(metric => metric.name) } }) const { control } = methods @@ -430,7 +458,46 @@ const MetricTagsForm = ({ : undefined }} - toggle={toggleAreYouSure} + toggle={ toggleAreYouSure } + extra_button={ + setDropdownOpen(!dropdownOpen) }> + CSV + + + Export + + { hiddenFileInput.current.click() }} + > + Import + + + { + PapaParse.parse(e.target.files[0], { + header: true, + complete: (results) => { + var imported = results.data; + // remove entries without keys if there is any + imported = imported.filter( + obj => "name" in obj && obj["name"] + ) + methods.resetField("view_metrics4tag") + methods.setValue("view_metrics4tag", imported.sort()) + methods.trigger("view_metrics4tag") + methods.resetField("searchItem") + methods.resetField("metrics4tag") + methods.setValue("metrics4tag", imported.sort()) + } + }) + }} + style={{display: 'none'}} + /> + + } > diff --git a/poem/Poem/frontend/react/__tests__/MetricTags.test.js b/poem/Poem/frontend/react/__tests__/MetricTags.test.js index 74d99af23..c7fc5cae0 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTags.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTags.test.js @@ -7,6 +7,7 @@ import { MetricTagsComponent, MetricTagsList } from "../MetricTags" import { Backend } from "../DataManager" import selectEvent from "react-select-event" import { NotificationManager } from "react-notifications" +import useEvent from '@testing-library/user-event'; jest.mock("../DataManager", () => { @@ -450,6 +451,7 @@ describe("Test metric tags changeview", () => { expect(screen.getByRole("button", { name: /delete/i })).toBeInTheDocument() expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + expect(screen.getByRole("button", { name: /csv/i })).toBeInTheDocument() }) test("Test filter metrics", async () => { @@ -862,7 +864,13 @@ describe("Test metric tags changeview", () => { const row2 = table.getAllByRole("row")[3] - await selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + }) + + await waitFor(() => { + expect(screen.queryByText("Must be one of predefined metrics")).not.toBeInTheDocument() + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { @@ -911,6 +919,10 @@ describe("Test metric tags changeview", () => { selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") }) + await waitFor(() => { + expect(screen.queryByText("Must be one of predefined metrics")).not.toBeInTheDocument() + }) + fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); @@ -935,6 +947,145 @@ describe("Test metric tags changeview", () => { ) }) + test("Test import csv successfully", async () => { + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /csv/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /import/i })) + + const csv = 'name\r\nargo.AMS-Check\r\nargo.AMSPublisher-Check\r\n'; + + const content = new Blob([csv], { type: "text/csv;charset=UTF-8" }) + const file = new File([content], "harmonized.csv", { type: "text/csv;charset=UTF-8" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + const nameField = screen.getByTestId("name") + + expect(nameField.value).toBe("harmonized") + expect(nameField).toBeEnabled() + + expect(screen.getByPlaceholderText(/search/i)).toBeInTheDocument() + const table = within(screen.getByRole("table")) + + expect(table.getAllByRole("columnheader")).toHaveLength(3) + expect(table.getByRole("columnheader", { name: "#" })).toBeInTheDocument() + expect(table.getByRole("columnheader", { name: /metric/i }).textContent).toBe("Metric template") + expect(table.getByRole("columnheader", { name: /action/i }).textContent).toBe("Actions") + + await waitFor(() => { + expect(table.getAllByRole("row")).toHaveLength(4) + }) + expect(table.getAllByTestId(/remove-/i)).toHaveLength(2) + expect(table.getAllByTestId(/insert-/i)).toHaveLength(2) + + expect(table.queryByText("generic.certificate.validity")).not.toBeInTheDocument() + expect(table.queryByText("generic.http.connect")).not.toBeInTheDocument() + expect(table.queryByText("generic.tcp.connect")).not.toBeInTheDocument() + + expect(table.getByText("argo.AMS-Check")).toBeInTheDocument() + expect(table.getByText("argo.AMSPublisher-Check")).toBeInTheDocument() + + selectEvent.openMenu(table.getByText("argo.AMS-Check")) + expect(table.getAllByText("generic.certificate.validity")).toHaveLength(1) + expect(table.getAllByText("generic.http.connect")).toHaveLength(1) + expect(table.getAllByText("generic.tcp.connect")).toHaveLength(1) + expect(table.getAllByText("argo.AMS-Check")).toHaveLength(1) + expect(table.getAllByText("argo.AMSPublisher-Check")).toHaveLength(1) + + expect(screen.getByRole("button", { name: /delete/i })).toBeInTheDocument() + expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + }) + + test("Test import csv with nonexisting metrics", async () => { + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /csv/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /import/i })) + + const csv = 'name\r\nargo.AMS-Check\r\nargo.AMSPublisher-Check\r\nmock.metric.name\r\n'; + + const content = new Blob([csv], { type: "text/csv;charset=UTF-8" }) + const file = new File([content], "harmonized.csv", { type: "text/csv;charset=UTF-8" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + const nameField = screen.getByTestId("name") + + expect(nameField.value).toBe("harmonized") + expect(nameField).toBeEnabled() + + expect(screen.getByPlaceholderText(/search/i)).toBeInTheDocument() + const table = within(screen.getByRole("table")) + + expect(table.getAllByRole("columnheader")).toHaveLength(3) + expect(table.getByRole("columnheader", { name: "#" })).toBeInTheDocument() + expect(table.getByRole("columnheader", { name: /metric/i }).textContent).toBe("Metric template") + expect(table.getByRole("columnheader", { name: /action/i }).textContent).toBe("Actions") + + await waitFor(() => { + expect(table.getAllByRole("row")).toHaveLength(5) + }) + expect(table.getAllByTestId(/remove-/i)).toHaveLength(3) + expect(table.getAllByTestId(/insert-/i)).toHaveLength(3) + + expect(table.queryByText("generic.certificate.validity")).not.toBeInTheDocument() + expect(table.queryByText("generic.http.connect")).not.toBeInTheDocument() + expect(table.queryByText("generic.tcp.connect")).not.toBeInTheDocument() + + expect(table.getByText("argo.AMS-Check")).toBeInTheDocument() + expect(table.getByText("argo.AMSPublisher-Check")).toBeInTheDocument() + expect(table.getByText("mock.metric.name")).toBeInTheDocument() + + selectEvent.openMenu(table.getByText("argo.AMS-Check")) + expect(table.getAllByText("generic.certificate.validity")).toHaveLength(1) + expect(table.getAllByText("generic.http.connect")).toHaveLength(1) + expect(table.getAllByText("generic.tcp.connect")).toHaveLength(1) + expect(table.getAllByText("argo.AMS-Check")).toHaveLength(1) + expect(table.getAllByText("argo.AMSPublisher-Check")).toHaveLength(1) + + await waitFor(() => { + expect(screen.queryByText('Must be one of predefined metrics')).toBeInTheDocument() + }) + + expect(screen.getByRole("button", { name: /delete/i })).toBeInTheDocument() + expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + }) + test("Test display warning messages", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ @@ -951,7 +1102,13 @@ describe("Test metric tags changeview", () => { expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() }) - await selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") + await waitFor(() => { + selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") + }) + + await waitFor(() => { + expect(screen.getByText("argo.AMS-Check")).toBeInTheDocument() + }) fireEvent.click(screen.getByTestId("remove-1")) @@ -965,6 +1122,10 @@ describe("Test metric tags changeview", () => { selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") }) + await waitFor(() => { + expect(screen.getByText("argo.AMSPublisher-Check")).toBeInTheDocument() + }) + fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); @@ -1016,6 +1177,10 @@ describe("Test metric tags changeview", () => { await selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") + await waitFor(() => { + expect(screen.getByText("argo.AMS-Check")).toBeInTheDocument() + }) + fireEvent.click(screen.getByTestId("remove-1")) fireEvent.click(screen.getByTestId("insert-0")) @@ -1028,6 +1193,14 @@ describe("Test metric tags changeview", () => { selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") }) + await waitFor(() => { + expect(screen.getByText("argo.AMSPublisher-Check")).toBeInTheDocument() + }) + + await waitFor(() => { + expect(screen.queryByText("Must be one of predefined metrics")).not.toBeInTheDocument() + }) + fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); From 774bebb26bf6a036a504a4ea25aff587deaf91a0 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 20 Jun 2024 09:18:18 +0200 Subject: [PATCH 18/22] Export data option for metric tags --- poem/Poem/frontend/react/MetricTags.js | 14 ++++- .../react/__tests__/MetricTags.test.js | 62 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/poem/Poem/frontend/react/MetricTags.js b/poem/Poem/frontend/react/MetricTags.js index f8c5f9503..afbbccac2 100644 --- a/poem/Poem/frontend/react/MetricTags.js +++ b/poem/Poem/frontend/react/MetricTags.js @@ -46,6 +46,7 @@ import { ListViewPlaceholder } from "./Placeholders" import PapaParse from 'papaparse'; +import { downloadCSV } from './FileDownload'; const validationSchema = yup.object().shape({ @@ -463,7 +464,18 @@ const MetricTagsForm = ({ setDropdownOpen(!dropdownOpen) }> CSV - + { + let csvContent = [] + metrics4tag.sort().forEach((metric) => { + csvContent.push({ name: metric.name }) + }) + const content = PapaParse.unparse(csvContent) + let filename = `${name}.csv` + downloadCSV(content, filename) + }} + disabled={ addview } + > Export { expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() }) + test("Test export csv successfully", async () => { + const helpers = require("../FileDownload") + jest.spyOn(helpers, "downloadCSV").mockReturnValueOnce(null) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /csv/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /export/i })) + + const content = "name\r\ngeneric.certificate.validity\r\ngeneric.http.connect\r\ngeneric.tcp.connect" + + expect(helpers.downloadCSV).toHaveBeenCalledTimes(1) + expect(helpers.downloadCSV).toHaveBeenCalledWith(content, "harmonized.csv") + }) + + test("Export csv when form has been changed", async () => { + const helpers = require("../FileDownload") + jest.spyOn(helpers, "downloadCSV").mockReturnValueOnce(null) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + await waitFor(() => { + selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") + }) + + await waitFor(() => { + expect(screen.getByText("argo.AMS-Check")).toBeInTheDocument() + }) + + fireEvent.click(screen.getByTestId("remove-1")) + + fireEvent.click(screen.getByTestId("insert-0")) + + const table = within(screen.getByRole("table")) + + const row2 = table.getAllByRole("row")[3] + + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + }) + + await waitFor(() => { + expect(screen.queryByText("Must be one of predefined metrics")).not.toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole("button", { name: /csv/i })) + fireEvent.click(screen.getByRole("menuitem", { name: /export/i })) + + const content = "name\r\nargo.AMS-Check\r\nargo.AMSPublisher-Check\r\ngeneric.tcp.connect" + + expect(helpers.downloadCSV).toHaveBeenCalledTimes(1) + expect(helpers.downloadCSV).toHaveBeenCalledWith(content, "harmonized.csv") + }) + test("Test display warning messages", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ From c3b818a3d6accd7cb7c84ab962a1c4a56df604a3 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Thu, 20 Jun 2024 09:26:24 +0200 Subject: [PATCH 19/22] Add tests for csv import in addview --- .../react/__tests__/MetricTags.test.js | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/poem/Poem/frontend/react/__tests__/MetricTags.test.js b/poem/Poem/frontend/react/__tests__/MetricTags.test.js index f3cf0bc91..31b56f4d2 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTags.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTags.test.js @@ -1516,6 +1516,7 @@ describe("Test metric tags addview", () => { expect(screen.queryByRole("button", { name: /delete/i })).not.toBeInTheDocument() expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + expect(screen.getByRole("button", { name: /csv/i })).toBeInTheDocument() }) test("Test change metric tags name", async () => { @@ -1649,6 +1650,141 @@ describe("Test metric tags addview", () => { ) }) + test("Test import csv successfully", async () => { + mockAddObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + const csv = 'name\r\nargo.AMS-Check\r\nargo.AMSPublisher-Check\r\n'; + + const content = new Blob([csv], { type: "text/csv;charset=UTF-8" }) + const file = new File([content], "harmonized.csv", { type: "text/csv;charset=UTF-8" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + fireEvent.change(screen.getByTestId("name"), { target: { value: "test_tag" } }) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /add/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockAddObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { name: "test_tag", metrics: ["argo.AMS-Check", "argo.AMSPublisher-Check"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully added", "Added", 2000 + ) + }) + + test("Test import csv, make some changes, and save", async () => { + mockAddObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + const csv = 'name\r\nargo.AMS-Check\r\nargo.AMSPublisher-Check\r\n'; + + const content = new Blob([csv], { type: "text/csv;charset=UTF-8" }) + const file = new File([content], "harmonized.csv", { type: "text/csv;charset=UTF-8" }) + const input = screen.getByTestId("file_input") + + await waitFor(() => { + useEvent.upload(input, file) + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId("file_input")) + }) + + fireEvent.change(screen.getByTestId("name"), { target: { value: "test_tag" } }) + + const table = within(screen.getByRole("table")) + + fireEvent.click(table.getByTestId("insert-1")) + + const row3 = table.getAllByRole("row")[4] + const input3 = within(row3).getByRole("combobox") + + await waitFor(() => { + selectEvent.select(input3, "generic.tcp.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.tcp.connect")).toBeInTheDocument() + }) + + fireEvent.click(table.getByTestId("remove-1")) + + await waitFor(() => { + expect(table.queryByText("argo.AMSPublisher-Check")).not.toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /add/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockAddObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { name: "test_tag", metrics: ["argo.AMS-Check", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully added", "Added", 2000 + ) + }) + test("Test display warning messages", async () => { mockAddObject.mockReturnValueOnce( Promise.resolve({ From 257f38f71f052b8e99cd37f1c3f797fd4b6ec927 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 25 Jun 2024 12:41:48 +0200 Subject: [PATCH 20/22] Remove dependency field from the UI --- poem/Poem/frontend/react/MetricTemplates.js | 2 +- poem/Poem/frontend/react/Metrics.js | 25 ++- .../react/__tests__/MetricTemplates.test.js | 211 ++++++++++-------- .../frontend/react/__tests__/Metrics.test.js | 42 ++-- 4 files changed, 141 insertions(+), 139 deletions(-) diff --git a/poem/Poem/frontend/react/MetricTemplates.js b/poem/Poem/frontend/react/MetricTemplates.js index fe8cb86fe..2bd45f536 100644 --- a/poem/Poem/frontend/react/MetricTemplates.js +++ b/poem/Poem/frontend/react/MetricTemplates.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { MetricForm} from './Metrics'; +import { MetricForm } from './Metrics'; import { Backend } from './DataManager'; import { NotifyOk, diff --git a/poem/Poem/frontend/react/Metrics.js b/poem/Poem/frontend/react/Metrics.js index 9132ed9a1..5a8727c23 100644 --- a/poem/Poem/frontend/react/Metrics.js +++ b/poem/Poem/frontend/react/Metrics.js @@ -1376,17 +1376,20 @@ export const MetricForm = addview={ addview } isPassive={ type === "Passive" } /> - + { + (watchDependency.length > 1 && watchDependency[0].key != "") && + + } { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByText(/select/i) + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('argo.AMS-Check'); expect(typeField).toBeEnabled() @@ -1498,9 +1580,7 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(screen.getByRole("heading", { name: /attributes/i })).toBeInTheDocument() expect(attributeKey.value).toBe('argo.ams_TOKEN'); expect(attributeVal.value).toBe('--token'); - expect(screen.getByRole("heading", { name: /dependency/i })).toBeInTheDocument() - expect(dependencyKey.value).toBe(''); - expect(dependencyVal.value).toBe(''); + expect(screen.queryByRole("heading", { name: /dependency/i })).not.toBeInTheDocument() expect(screen.getByRole("heading", { name: /parameter/i })).toBeInTheDocument() expect(parameterKey.value).toBe('--project'); expect(parameterVal.value).toBe('EGI'); @@ -1519,7 +1599,7 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(screen.getByTestId("attributes.addnew")) expect(screen.queryByTestId("dependency")).not.toBeInTheDocument() - expect(screen.getByTestId("dependency.addnew")).toBeInTheDocument() + expect(screen.queryByTestId("dependency.addnew")).not.toBeInTheDocument() expect(screen.getByTestId("parameter.0.remove")).toBeInTheDocument() expect(screen.getByTestId("parameter.addnew")).toBeInTheDocument() @@ -1576,8 +1656,8 @@ describe('Test metric template changeview on SuperPOEM', () => { const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); @@ -1652,11 +1732,7 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(screen.queryByTestId('attributes.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('attributes.addnew')).not.toBeInTheDocument(); - expect(screen.getByRole("heading", { name: /dependency/i })).toBeInTheDocument() - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() + expect(screen.queryByRole("heading", { name: /dependency/i })).not.toBeInTheDocument() expect(screen.queryByTestId('dependency.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('dependency.addnew')).not.toBeInTheDocument(); @@ -1895,6 +1971,7 @@ describe('Test metric template changeview on SuperPOEM', () => { ) }) + /* test("Test change dependency", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ ok: true, status: 200 }) @@ -1963,6 +2040,7 @@ describe('Test metric template changeview on SuperPOEM', () => { 'Metric template successfully changed', 'Changed', 2000 ) }) + */ test("Test change parameter", async () => { mockChangeObject.mockReturnValueOnce( @@ -2128,9 +2206,6 @@ describe('Test metric template changeview on SuperPOEM', () => { fireEvent.change(screen.getByTestId('attributes.1.key'), { target: { value: 'ATTRIBUTE' } }); fireEvent.change(screen.getByTestId('attributes.1.value'), { target: { value: '--meh' } }); - fireEvent.change(screen.getByTestId("dependency.0.key"), { target: { value: 'some-dep' } }); - fireEvent.change(screen.getByTestId("dependency.0.value"), { target: { value: 'some-dep-value' } }); - fireEvent.click(screen.getByTestId('parameter.0.remove')); fireEvent.click(screen.getByTestId('flags.addnew')); @@ -2166,9 +2241,7 @@ describe('Test metric template changeview on SuperPOEM', () => { { key: 'argo.ams_TOKEN', value: '--token' }, { key: 'ATTRIBUTE', value: '--meh', isNew: true } ], - 'dependency': [ - { key: 'some-dep', value: 'some-dep-value' } - ], + 'dependency': [{ key: "", value: "" }], 'parameter': [{ key: '', value: '' }], 'flags': [ { key: 'OBSESS', value: '1' }, @@ -2217,9 +2290,6 @@ describe('Test metric template changeview on SuperPOEM', () => { fireEvent.change(screen.getByTestId('attributes.1.key'), { target: { value: 'ATTRIBUTE' } }); fireEvent.change(screen.getByTestId('attributes.1.value'), { target: { value: '--meh' } }); - fireEvent.change(screen.getByTestId("dependency.0.key"), { target: { value: 'some-dep' } }); - fireEvent.change(screen.getByTestId("dependency.0.value"), { target: { value: 'some-dep-value' } }); - fireEvent.click(screen.getByTestId('parameter.0.remove')); fireEvent.click(screen.getByTestId('flags.addnew')); @@ -2255,9 +2325,7 @@ describe('Test metric template changeview on SuperPOEM', () => { { key: 'argo.ams_TOKEN', value: '--token' }, { key: 'ATTRIBUTE', value: '--meh', isNew: true } ], - 'dependency': [ - { key: 'some-dep', value: 'some-dep-value' } - ], + 'dependency': [{ key: "", value: "" }], 'parameter': [{ key: '', value: '' }], 'flags': [ { key: 'OBSESS', value: '1' }, @@ -2702,8 +2770,6 @@ describe('Test metric template changeview on SuperPOEM', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey1 = screen.getByTestId('flags.0.key'); @@ -2712,6 +2778,9 @@ describe('Test metric template changeview on SuperPOEM', () => { const flagVal2 = screen.queryByTestId('flags.1.value'); const parentField = screen.getAllByText(/select/i)[2] + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('org.apel.APEL-Pub'); expect(probeField).toBeEnabled(); @@ -2763,10 +2832,6 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(attributeKey).not.toHaveAttribute('hidden'); expect(attributeVal.value).toBe(''); expect(attributeVal).not.toHaveAttribute('hidden'); - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).not.toHaveAttribute('hidden'); - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).not.toHaveAttribute('hidden'); expect(parameterKey.value).toBe(''); expect(parameterKey).not.toHaveAttribute('hidden'); expect(parameterVal.value).toBe(''); @@ -2943,8 +3008,8 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(configVal5a).toBeEnabled() expect(attributeKey2.value).toBe('argo.ams_TOKEN'); expect(attributeVal2.value).toBe('--token'); - expect(dependencyKey2.value).toBe(''); - expect(dependencyVal2.value).toBe(''); + expect(dependencyKey2).not.toBeInTheDocument() + expect(dependencyVal2).not.toBeInTheDocument() expect(parameterKey2.value).toBe('--project'); expect(parameterVal2.value).toBe('EGI') expect(flagKey1a.value).toBe('OBSESS'); @@ -3070,14 +3135,15 @@ describe('Test metric template addview on SuperPOEM', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getAllByText(/select/i)[2] + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe(''); expect(typeField).toBeEnabled() @@ -3132,8 +3198,6 @@ describe('Test metric template addview on SuperPOEM', () => { expect(configVal5).toBeEnabled() expect(attributeKey.value).toBe(''); expect(attributeVal.value).toBe(''); - expect(dependencyKey.value).toBe(''); - expect(dependencyVal.value).toBe(''); expect(parameterKey.value).toBe(''); expect(parameterVal.value).toBe(''); expect(flagKey.value).toBe(''); @@ -3175,8 +3239,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3206,8 +3268,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3238,8 +3298,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3268,8 +3326,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "1", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3300,8 +3356,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3327,8 +3381,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "attribute1", "attributes.0.value": "120", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3357,8 +3409,6 @@ describe('Test metric template addview on SuperPOEM', () => { "attributes.0.value": "120", "attributes.1.key": "attribute2", "attributes.1.value": "value3", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3383,8 +3433,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "attribute1", "attributes.0.value": "120", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3410,8 +3458,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "ATTRIBUTE", "attributes.0.value": "123", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3436,8 +3482,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3445,6 +3489,7 @@ describe('Test metric template addview on SuperPOEM', () => { }) }) + /* test("Test add dependency", async () => { renderAddView(); @@ -3611,6 +3656,7 @@ describe('Test metric template addview on SuperPOEM', () => { "flags.0.value": "" }) }) + */ test("Test add parameter", async () => { renderAddView(); @@ -3635,8 +3681,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3662,8 +3706,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "-vv", "parameter.0.value": "", "flags.0.key": "", @@ -3690,8 +3732,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "-vv", "parameter.0.value": "", "parameter.1.key": "-p", @@ -3718,8 +3758,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "-p", "parameter.0.value": "443", "flags.0.key": "", @@ -3744,8 +3782,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "-p", "parameter.0.value": "80", "flags.0.key": "", @@ -3770,8 +3806,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3802,8 +3836,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -3829,8 +3861,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "OBSESS", @@ -3857,8 +3887,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "OBSESS", @@ -3885,8 +3913,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "NOHOSTNAME", @@ -3911,8 +3937,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "NOHOSTNAME", @@ -3937,8 +3961,6 @@ describe('Test metric template addview on SuperPOEM', () => { "config.4.value": "", "attributes.0.key": "", "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", "parameter.0.key": "", "parameter.0.value": "", "flags.0.key": "", @@ -4516,14 +4538,15 @@ describe('Test metric template cloneview on SuperPOEM', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByText(/select/i) + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('argo.AMS-Check'); expect(typeField).toBeEnabled() @@ -4577,8 +4600,6 @@ describe('Test metric template cloneview on SuperPOEM', () => { expect(configVal5).toBeEnabled() expect(attributeKey.value).toBe('argo.ams_TOKEN'); expect(attributeVal.value).toBe('--token'); - expect(dependencyKey.value).toBe(''); - expect(dependencyVal.value).toBe(''); expect(parameterKey.value).toBe('--project'); expect(parameterVal.value).toBe('EGI'); expect(flagKey.value).toBe('OBSESS'); @@ -4911,14 +4932,15 @@ describe('Test metric template detail view on tenant POEM', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByTestId('parent'); + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('argo.AMS-Check'); expect(nameField).toBeDisabled() expect(typeField.value).toBe('Active'); @@ -4967,10 +4989,6 @@ describe('Test metric template detail view on tenant POEM', () => { expect(attributeVal).toBeDisabled() expect(screen.queryByTestId('attributes.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('attributes.addnew')).not.toBeInTheDocument(); - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() expect(screen.queryByTestId('dependency.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('dependency.addnew')).not.toBeInTheDocument(); expect(parameterKey.value).toBe('--project'); @@ -5022,7 +5040,7 @@ describe('Test metric template version detail view', () => { renderVersionDetailsView(); await waitFor(() => { - expect(screen.getByTestId("dependency.0.key")).toBeInTheDocument() + expect(screen.getByTestId("config.0.key")).toBeInTheDocument() }) const nameField = screen.getByTestId('name'); @@ -5044,14 +5062,15 @@ describe('Test metric template version detail view', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByTestId('parent'); + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('argo.AMS-Check'); expect(nameField).toBeDisabled() expect(typeField.value).toBe('Active'); @@ -5100,10 +5119,6 @@ describe('Test metric template version detail view', () => { expect(attributeVal).toBeDisabled() expect(screen.queryByTestId('attributes.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('attributes.addnew')).not.toBeInTheDocument(); - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() expect(screen.queryByTestId('dependency.0.remove')).not.toBeInTheDocument(); expect(screen.queryByTestId('dependency.addnew')).not.toBeInTheDocument(); expect(parameterKey.value).toBe('--project'); diff --git a/poem/Poem/frontend/react/__tests__/Metrics.test.js b/poem/Poem/frontend/react/__tests__/Metrics.test.js index 08768e7f1..58ddbc507 100644 --- a/poem/Poem/frontend/react/__tests__/Metrics.test.js +++ b/poem/Poem/frontend/react/__tests__/Metrics.test.js @@ -817,13 +817,8 @@ describe('Tests for metric change', () => { expect(attributeVal.value).toBe('--token'); expect(attributeVal).toBeDisabled() - const dependencyKey = screen.getByTestId('dependency.0.key'); - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - - const dependencyVal = screen.getByTestId('dependency.0.value'); - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() const parameterKey = screen.getByTestId('parameter.0.key'); expect(parameterKey.value).toBe('--project'); @@ -873,7 +868,7 @@ describe('Tests for metric change', () => { renderChangeView({ publicView: true }); await waitFor(() => { - expect(screen.getByTestId("dependency.0.key")).toBeInTheDocument() + expect(screen.getByTestId("config.0.key")).toBeInTheDocument() }) expect(screen.getByRole('heading', { name: /details/i }).textContent).toBe('Metric details'); @@ -961,13 +956,8 @@ describe('Tests for metric change', () => { expect(attributeVal.value).toBe('--token'); expect(attributeVal).toBeDisabled() - const dependencyKey = screen.getByTestId('dependency.0.key'); - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - - const dependencyVal = screen.getByTestId('dependency.0.value'); - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() const parameterKey = screen.getByTestId('parameter.0.key'); expect(parameterKey.value).toBe('--project'); @@ -1466,7 +1456,7 @@ describe('Tests for metric change', () => { renderChangeView(); await waitFor(() => { - expect(screen.getByTestId("dependency.0.key")).toBeInTheDocument() + expect(screen.getByTestId("config.0.key")).toBeInTheDocument() }) const nameField = screen.getByTestId('name'); @@ -1488,13 +1478,14 @@ describe('Tests for metric change', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByTestId('parent'); + + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() expect(screen.getByRole('alert').textContent).toBe( 'This is a read-only instance, please request the corresponding permissions to perform any changes in this form.' @@ -1544,10 +1535,6 @@ describe('Tests for metric change', () => { expect(attributeKey).toBeDisabled() expect(attributeVal.value).toBe('--token'); expect(attributeVal).toBeDisabled() - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() expect(parameterKey.value).toBe('--project'); expect(parameterKey).toBeDisabled() expect(parameterVal.value).toBe('EGI'); @@ -1586,7 +1573,7 @@ describe('Tests for metric history', () => { renderVersionDetailsView(); await waitFor(() => { - expect(screen.getByTestId("dependency.0.key")).toBeInTheDocument() + expect(screen.getByTestId("config.0.key")).toBeInTheDocument() }) expect(screen.getByRole('heading', { name: /argo/i }).textContent).toBe('argo.AMS-Check (2020-11-30 13:23:48)'); @@ -1610,14 +1597,15 @@ describe('Tests for metric history', () => { const configVal5 = screen.getByTestId('config.4.value'); const attributeKey = screen.getByTestId('attributes.0.key'); const attributeVal = screen.getByTestId('attributes.0.value') - const dependencyKey = screen.getByTestId('dependency.0.key'); - const dependencyVal = screen.getByTestId('dependency.0.value'); const parameterKey = screen.getByTestId('parameter.0.key'); const parameterVal = screen.getByTestId('parameter.0.value'); const flagKey = screen.getByTestId('flags.0.key'); const flagVal = screen.getByTestId('flags.0.value'); const parentField = screen.getByTestId('parent'); + expect(screen.queryByTestId("dependency.0.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("dependency.0.value")).not.toBeInTheDocument() + expect(nameField.value).toBe('argo.AMS-Check'); expect(nameField).toBeDisabled() expect(typeField.value).toBe('Active'); @@ -1659,10 +1647,6 @@ describe('Tests for metric history', () => { expect(attributeKey).toBeDisabled() expect(attributeVal.value).toBe('--token'); expect(attributeVal).toBeDisabled() - expect(dependencyKey.value).toBe(''); - expect(dependencyKey).toBeDisabled() - expect(dependencyVal.value).toBe(''); - expect(dependencyVal).toBeDisabled() expect(parameterKey.value).toBe('--project'); expect(parameterKey).toBeDisabled() expect(parameterVal.value).toBe('EGI'); From b9a660321abeaa51720158f92b225af238108997 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Wed, 26 Jun 2024 11:34:09 +0200 Subject: [PATCH 21/22] Handle form when having metric (template) with dependency --- poem/Poem/frontend/react/Metrics.js | 23 +- .../react/__tests__/MetricTemplates.test.js | 734 ++++++++++-------- .../frontend/react/__tests__/Metrics.test.js | 424 +++++++++- 3 files changed, 849 insertions(+), 332 deletions(-) diff --git a/poem/Poem/frontend/react/Metrics.js b/poem/Poem/frontend/react/Metrics.js index 5a8727c23..a72f2745d 100644 --- a/poem/Poem/frontend/react/Metrics.js +++ b/poem/Poem/frontend/react/Metrics.js @@ -186,7 +186,7 @@ const InlineFields = ({ id={ `${fieldname}.${index}.key` } data-testid={ `${fieldname}.${index}.key` } className={ `form-control ${entry.isNew && "border-success"}` } - disabled={ readOnly || fieldname === "config" || (isPassive && entry.key === "PASSIVE") } + disabled={ readOnly || fieldname === "config" || fieldname === "dependency" || (isPassive && entry.key === "PASSIVE") } /> } /> @@ -201,7 +201,7 @@ const InlineFields = ({ id={ `${fieldname}.${index}.value` } data-testid={ `${fieldname}.${index}.value` } className={ `form-control ${entry.isNew && "border-success"} ${ errors?.config?.[index]?.value && "is-invalid" }` } - disabled={ readOnly || (isPassive && entry.key === "PASSIVE") || (fieldname === "config" && entry.key === "path" && isMetric) } + disabled={ readOnly || (isPassive && entry.key === "PASSIVE") || (fieldname === "config" && entry.key === "path" && isMetric) || fieldname === "dependency" } /> } /> @@ -1377,7 +1377,7 @@ export const MetricForm = isPassive={ type === "Passive" } /> { - (watchDependency.length > 1 && watchDependency[0].key != "") && + (watchDependency.length >= 1 && watchDependency[0].key != "") && @@ -1704,6 +1704,8 @@ export const MetricChange = (props) => { const probe = probes ? probes.find(prb => prb.object_repr === metric.probeversion).fields : { package: "" }; + const emptyEntry = [ { key: "", value: "" } ] + return ( { probeexecutable: metric.probeexecutable, parent: metric.parent, config: metric.config, - attributes: metric.attribute, - dependency: metric.dependancy, - parameter: metric.parameter, - flags: metric.flags, - files: metric.files, - file_attributes: metric.files, - file_parameters: metric.fileparameter, + attributes: metric.attribute.length > 0 ? metric.attribute : emptyEntry, + dependency: metric.dependancy.length > 0 ? metric.dependancy : emptyEntry, + parameter: metric.parameter.length > 0 ? metric.parameter : emptyEntry, + flags: metric.flags.length > 0 ? metric.flags : emptyEntry, + file_attributes: metric.files.length > 0 ? metric.files : emptyEntry, + file_parameters: metric.fileparameter.length > 0 ? metric.fileparameter : emptyEntry, probe: probe, tags: metric.tags, profiles: metric.profiles diff --git a/poem/Poem/frontend/react/__tests__/MetricTemplates.test.js b/poem/Poem/frontend/react/__tests__/MetricTemplates.test.js index d40b03d32..cb716dfdf 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTemplates.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTemplates.test.js @@ -293,84 +293,73 @@ const mockMetricTemplate = { }; const mockMetricTemplateWithDependency = { - "id": 422, - "name": "srce.gridproxy.validity", - "mtype": "Active", - "tags": [ - "argo", - "authentication", - "htc", - "internal", - "monitoring", - "proxy certificate" - ], - "probeversion": "GridProxy-probe (0.3.0)", - "description": "", - "parent": "", - "probeexecutable": "GridProxy-probe", - "config": [ - { - "key": "maxCheckAttempts", - "value": "3" - }, - { - "key": "timeout", - "value": "30" - }, - { - "key": "path", - "value": "/usr/libexec/argo/probes/globus" - }, - { - "key": "interval", - "value": "15" - }, - { - "key": "retryInterval", - "value": "3" - } - ], - "attribute": [ - { - "key": "VONAME", - "value": "--vo" - }, - { - "key": "X509_USER_PROXY", - "value": "-x" - } - ], - "dependency": [ - { - "key": "hr.srce.GridProxy-Get", - "value": "0" - } - ], - "flags": [ - { - "key": "NOHOSTNAME", - "value": "1" - }, - { - "key": "NRPE", - "value": "1" - }, - { - "key": "LOCALDEP", - "value": "1" - }, - { - "key": "VO", - "value": "1" - }, - { - "key": "NOPUBLISH", - "value": "1" - } - ], - "files": [], - "parameter": [], - "fileparameter": [] + id: 422, + name: "srce.gridproxy.validity", + mtype: "Active", + tags: [ + "test_tag1", + "test_tag2", + "internal" + ], + probeversion: "GridProxy-probe (0.2.0)", + description: "", + parent: "", + probeexecutable: "GridProxy-probe", + config: [ + { + key: "maxCheckAttempts", + value: "3" + }, + { + key: "timeout", + value: "30" + }, + { + key: "path", + value: "/usr/libexec/argo/probes/globus" + }, + { + key: "interval", + value: "15" + }, + { + key: "retryInterval", + value: "3" + } + ], + attribute: [ + { + key: "VONAME", + value: "--vo" + }, + { + key: "X509_USER_PROXY", + value: "-x" + } + ], + dependency: [ + { + key: "hr.srce.GridProxy-Get", + value: "0" + } + ], + flags: [ + { + key: "NOHOSTNAME", + value: "1" + }, + { + key: "VO", + value: "1" + }, + { + key: "NOPUBLISH", + value: "1" + } + ], + files: [], + parameter: [], + fileparameter: [] } const mockProbeVersions = [ @@ -458,7 +447,24 @@ const mockProbeVersions = [ date_created: '2020-12-31 08:57:15', comment: 'Newest version', version: '0.1.13' - } + }, + { + id: '64', + object_repr: "GridProxy-probe (0.2.0)", + fields: { + name: "GridProxy-probe", + version: "0.2.0", + package: "argo-probe-globus (0.2.0)", + description: "Probe for functional checking of MyProxy service.", + comment: "Harmonized version.", + repository: "https://github.com/ARGOeu-Metrics/argo-probe-globus", + docurl: "https://github.com/ARGOeu-Metrics/argo-probe-globus/blob/master/README.md" + }, + user: "poem", + date_created: "2019-12-09 09:24:20", + comment: "Initial version.", + version: "0.2.0" +}, ]; const mockPassiveMetricTemplate = { @@ -728,8 +734,11 @@ function renderTenantListView() { function renderChangeView(options = {}) { const passive = options.passive ? options.passive : false; const publicView = options.publicView ? options.publicView : false; + const withDependency = options.withDependency ? options.withDependency : false - const route = `/ui/${publicView ? 'public_' : ''}metrictemplates/${passive ? 'org.apel.APEL-Pub' : 'argo.AMS-Check'}`; + const metric = withDependency ? "srce.gridproxy.validity" : passive ? "org.apel.APEL-Pub" : "argo.AMS-Check" + + const route = `/ui/${publicView ? 'public_' : ''}metrictemplates/${metric}` if (publicView) return { @@ -1455,6 +1464,12 @@ describe('Test metric template changeview on SuperPOEM', () => { case '/api/v2/internal/public_metrictemplates/argo.AMS-Check': return Promise.resolve(mockMetricTemplate) + case '/api/v2/internal/metrictemplates/srce.gridproxy.validity': + return Promise.resolve(mockMetricTemplateWithDependency) + + case '/api/v2/internal/public_metrictemplates/srce.gridproxy.validity': + return Promise.resolve(mockMetricTemplateWithDependency) + case '/api/v2/internal/metrictemplates/org.apel.APEL-Pub': return Promise.resolve(mockPassiveMetricTemplate) @@ -1619,6 +1634,158 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument(); }) + test('Test that page renders properly if metric template with dependency', async () => { + renderChangeView({ withDependency: true }); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument(); + }) + + expect(screen.getByRole('heading', { name: /change metric/i }).textContent).toBe('Change metric template'); + + const nameField = screen.getByTestId('name'); + const typeField = screen.getByText('Active') + const probeField = screen.getByText('GridProxy-probe (0.2.0)') + const packageField = screen.getByTestId('package'); + const descriptionField = screen.getByTestId('description'); + const groupField = screen.queryByText(/group/i) + const tagsElement = screen.getByLabelText('Tags:') + + const executableField = screen.getByTestId('probeexecutable'); + const configKey1 = screen.getByTestId('config.0.key'); + const configKey2 = screen.getByTestId('config.1.key'); + const configKey3 = screen.getByTestId('config.2.key'); + const configKey4 = screen.getByTestId('config.3.key'); + const configKey5 = screen.getByTestId('config.4.key'); + const configVal1 = screen.getByTestId('config.0.value'); + const configVal2 = screen.getByTestId('config.1.value'); + const configVal3 = screen.getByTestId('config.2.value'); + const configVal4 = screen.getByTestId('config.3.value'); + const configVal5 = screen.getByTestId('config.4.value'); + const attributeKey1 = screen.getByTestId('attributes.0.key'); + const attributeVal1 = screen.getByTestId('attributes.0.value') + const attributeKey2 = screen.getByTestId('attributes.1.key'); + const attributeVal2 = screen.getByTestId('attributes.1.value') + const dependencyKey = screen.getByTestId("dependency.0.key") + const dependencyVal = screen.getByTestId("dependency.0.value") + const parameterKey = screen.getByTestId('parameter.0.key'); + const parameterVal = screen.getByTestId('parameter.0.value'); + const flagKey1 = screen.getByTestId('flags.0.key'); + const flagVal1 = screen.getByTestId('flags.0.value'); + const flagKey2 = screen.getByTestId('flags.1.key'); + const flagVal2 = screen.getByTestId('flags.1.value'); + const flagKey3 = screen.getByTestId('flags.2.key'); + const flagVal3 = screen.getByTestId('flags.2.value'); + const parentField = screen.getByText(/select/i) + + expect(nameField.value).toBe("srce.gridproxy.validity"); + expect(typeField).toBeEnabled() + + expect(screen.queryByText('Passive')).not.toBeInTheDocument() + selectEvent.openMenu(typeField) + expect(screen.getByText('Passive')).toBeInTheDocument() + + expect(probeField).toBeEnabled() + + expect(screen.queryByText('ams-probe (0.1.11)')).not.toBeInTheDocument() + expect(screen.queryByText('ams-publisher-probe (0.1.11)')).not.toBeInTheDocument() + expect(screen.queryByText('ams-publisher-probe (0.1.12)')).not.toBeInTheDocument() + expect(screen.queryByText('ams-probe-new (0.1.13)')).not.toBeInTheDocument() + selectEvent.openMenu(probeField) + expect(screen.queryByText('ams-probe (0.1.11)')).toBeInTheDocument() + expect(screen.queryByText('ams-publisher-probe (0.1.11)')).toBeInTheDocument() + expect(screen.queryByText('ams-publisher-probe (0.1.12)')).toBeInTheDocument() + expect(screen.queryByText('ams-probe-new (0.1.13)')).toBeInTheDocument() + + expect(packageField.value).toBe('argo-probe-globus (0.2.0)') + expect(packageField).toBeDisabled(); + expect(descriptionField.value).toBe("") + expect(groupField).not.toBeInTheDocument(); + + expect(tagsElement).toBeInTheDocument() + expect(screen.getByText("test_tag1")).toBeInTheDocument() + expect(screen.getByText("test_tag2")).toBeInTheDocument() + expect(screen.queryByText("internal")).toBeInTheDocument() + expect(screen.queryByText("deprecated")).not.toBeInTheDocument() + + expect(screen.getByRole("heading", { name: /probe executable/i })).toBeInTheDocument() + expect(executableField.value).toBe('GridProxy-probe'); + expect(screen.getByRole("heading", { name: "config" })).toBeInTheDocument() + expect(configKey1.value).toBe('maxCheckAttempts'); + expect(configKey1).toBeDisabled() + expect(configVal1.value).toBe('3'); + expect(configVal1).toBeEnabled() + expect(configKey2.value).toBe('timeout'); + expect(configKey2).toBeDisabled() + expect(configVal2.value).toBe('30'); + expect(configVal2).toBeEnabled() + expect(configKey3.value).toBe('path'); + expect(configKey3).toBeDisabled() + expect(configVal3.value).toBe('/usr/libexec/argo/probes/globus'); + expect(configVal3).toBeEnabled() + expect(configKey4.value).toBe('interval'); + expect(configKey4).toBeDisabled() + expect(configVal4.value).toBe('15'); + expect(configVal4).toBeEnabled() + expect(configKey5.value).toBe('retryInterval'); + expect(configKey5).toBeDisabled() + expect(configVal5.value).toBe('3'); + expect(configVal5).toBeEnabled() + expect(screen.getByRole("heading", { name: /attributes/i })).toBeInTheDocument() + expect(attributeKey1.value).toBe('VONAME'); + expect(attributeVal1.value).toBe('--vo'); + expect(attributeKey2.value).toBe('X509_USER_PROXY'); + expect(attributeVal2.value).toBe('-x'); + expect(screen.getByRole("heading", { name: /dependency/i })).toBeInTheDocument() + expect(dependencyKey.value).toBe("hr.srce.GridProxy-Get") + expect(dependencyVal.value).toBe("0") + expect(dependencyKey).toBeDisabled() + expect(dependencyVal).toBeDisabled() + expect(screen.getByRole("heading", { name: /parameter/i })).toBeInTheDocument() + expect(parameterKey.value).toBe(""); + expect(parameterVal.value).toBe(""); + expect(screen.getByRole("heading", { name: /flags/i })).toBeInTheDocument() + expect(flagKey1.value).toBe("NOHOSTNAME") + expect(flagVal1.value).toBe("1") + expect(flagKey2.value).toBe("VO") + expect(flagVal2.value).toBe("1") + expect(flagKey3.value).toBe("NOPUBLISH") + expect(flagVal3.value).toBe("1") + + expect(screen.queryByTestId('config.0.remove')).not.toBeInTheDocument() + expect(screen.queryByTestId('config.1.remove')).not.toBeInTheDocument() + expect(screen.queryByTestId('config.2.remove')).not.toBeInTheDocument() + expect(screen.queryByTestId('config.3.remove')).not.toBeInTheDocument() + expect(screen.queryByTestId('config.4.remove')).not.toBeInTheDocument() + expect(screen.queryByTestId('config.addnew')).not.toBeInTheDocument() + + expect(screen.getByTestId("attributes.0.remove")).toBeInTheDocument() + expect(screen.getByTestId("attributes.1.remove")).toBeInTheDocument() + expect(screen.getByTestId("attributes.addnew")) + + expect(screen.queryByTestId("dependency.0.remove")).toBeInTheDocument() + expect(screen.queryByTestId("dependency.addnew")).not.toBeInTheDocument() + + expect(screen.queryByTestId("parameter.0.remove")).not.toBeInTheDocument() + expect(screen.getByTestId("parameter.addnew")).toBeInTheDocument() + + expect(screen.getByTestId("flags.0.remove")).toBeInTheDocument() + expect(screen.getByTestId("flags.1.remove")).toBeInTheDocument() + expect(screen.getByTestId("flags.2.remove")).toBeInTheDocument() + expect(screen.getByTestId("flags.addnew")).toBeInTheDocument() + + expect(screen.getByRole("heading", { name: /parent/i })).toBeInTheDocument() + expect(screen.queryByText('argo.AMS-Publisher')).not.toBeInTheDocument() + expect(screen.queryByText('org.apel.APEL-Pub')).not.toBeInTheDocument() + selectEvent.openMenu(parentField) + expect(screen.queryByText('argo.AMS-Publisher')).toBeInTheDocument() + expect(screen.queryByText('org.apel.APEL-Pub')).toBeInTheDocument() + + expect(screen.getByRole('button', { name: /history/i }).closest('a')).toHaveAttribute('href', '/ui/metrictemplates/srce.gridproxy.validity/history'); + expect(screen.getByRole('button', { name: /clone/i }).closest('a')).toHaveAttribute('href', '/ui/metrictemplates/srce.gridproxy.validity/clone'); + expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument(); + }) + test('Test that public changeview for active metric template renders properly', async () => { renderChangeView({ publicView: true }); @@ -1762,6 +1929,173 @@ describe('Test metric template changeview on SuperPOEM', () => { expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument(); }) + test('Test that public changeview for active metric template renders properly if dependency', async () => { + renderChangeView({ publicView: true, withDependency: true }); + + await waitFor(() => { + expect(screen.getByTestId("name")).toBeInTheDocument() + }) + + expect(screen.getByRole('heading', { name: /metric template/i }).textContent).toBe('Metric template details'); + + const nameField = screen.getByTestId('name'); + const typeField = screen.getByTestId('mtype'); + const probeField = screen.getByTestId('probeversion') + const packageField = screen.getByTestId('package'); + const descriptionField = screen.getByTestId('description'); + const groupField = screen.queryByText(/group/i); + + const tagBadge1 = screen.getByText(/test_tag1/i); + const tagBadge2 = screen.getByText(/test_tag2/i); + const tagBadge3 = screen.queryByText("internal") + const tagBadge4 = screen.queryByText("deprecated") + + const executableField = screen.getByTestId('probeexecutable'); + + const configKey1 = screen.getByTestId('config.0.key'); + const configKey2 = screen.getByTestId('config.1.key'); + const configKey3 = screen.getByTestId('config.2.key'); + const configKey4 = screen.getByTestId('config.3.key'); + const configKey5 = screen.getByTestId('config.4.key'); + const configVal1 = screen.getByTestId('config.0.value'); + const configVal2 = screen.getByTestId('config.1.value'); + const configVal3 = screen.getByTestId('config.2.value'); + const configVal4 = screen.getByTestId('config.3.value'); + const configVal5 = screen.getByTestId('config.4.value'); + + const attributeKey1 = screen.getByTestId('attributes.0.key'); + const attributeVal1 = screen.getByTestId('attributes.0.value') + const attributeKey2 = screen.getByTestId('attributes.1.key'); + const attributeVal2 = screen.getByTestId('attributes.1.value') + + const dependencyKey = screen.getByTestId("dependency.0.key") + const dependencyVal = screen.getByTestId("dependency.0.value") + + const parameterKey = screen.getByTestId('parameter.0.key'); + const parameterVal = screen.getByTestId('parameter.0.value'); + + const flagKey1 = screen.getByTestId('flags.0.key') + const flagVal1 = screen.getByTestId('flags.0.value') + const flagKey2 = screen.getByTestId('flags.1.key') + const flagVal2 = screen.getByTestId('flags.1.value') + const flagKey3 = screen.getByTestId('flags.2.key') + const flagVal3 = screen.getByTestId('flags.2.value') + + const parentField = screen.getByTestId('parent'); + + expect(nameField.value).toBe('srce.gridproxy.validity'); + expect(nameField).toBeDisabled() + + expect(typeField.value).toBe('Active'); + expect(typeField).toBeDisabled() + expect(screen.queryByRole('option', { name: /active/i })).not.toBeInTheDocument() + expect(screen.queryByRole('option', { name: /passive/i })).not.toBeInTheDocument() + + expect(probeField.value).toBe('GridProxy-probe (0.2.0)'); + expect(probeField).toBeDisabled(); + + expect(packageField.value).toBe('argo-probe-globus (0.2.0)') + expect(packageField).toBeDisabled(); + + expect(descriptionField.value).toBe("") + expect(descriptionField).toBeDisabled(); + + expect(groupField).not.toBeInTheDocument(); + + expect(tagBadge1.textContent).toBe('test_tag1') + expect(tagBadge2.textContent).toBe('test_tag2') + expect(tagBadge3.textContent).toBe("internal") + expect(tagBadge4).not.toBeInTheDocument() + + expect(screen.getByRole("heading", { name: /probe executable/i })).toBeInTheDocument() + expect(executableField.value).toBe("GridProxy-probe") + expect(executableField).toBeDisabled() + + expect(screen.getByRole("heading", { name: "config" })).toBeInTheDocument() + expect(configKey1.value).toBe('maxCheckAttempts'); + expect(configKey1).toBeDisabled() + expect(configVal1.value).toBe('3'); + expect(configVal1).toBeDisabled() + expect(screen.queryByTestId('config.0.remove')).not.toBeInTheDocument(); + expect(configKey2.value).toBe('timeout'); + expect(configKey2).toBeDisabled() + expect(configVal2.value).toBe('30'); + expect(configVal2).toBeDisabled() + expect(screen.queryByTestId('config.1.remove')).not.toBeInTheDocument(); + expect(configKey3.value).toBe('path'); + expect(configKey3).toBeDisabled() + expect(configVal3.value).toBe('/usr/libexec/argo/probes/globus'); + expect(configVal3).toBeDisabled() + expect(screen.queryByTestId('config.2.remove')).not.toBeInTheDocument(); + expect(configKey4.value).toBe('interval'); + expect(configKey4).toBeDisabled() + expect(configVal4.value).toBe('15'); + expect(configVal4).toBeDisabled() + expect(screen.queryByTestId('config.3.remove')).not.toBeInTheDocument(); + expect(configKey5.value).toBe('retryInterval'); + expect(configKey5).toBeDisabled() + expect(configVal5.value).toBe('3'); + expect(configVal5).toBeDisabled() + expect(screen.queryByTestId('config.4.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('config.addnew')).not.toBeInTheDocument(); + + expect(screen.getByRole("heading", { name: /attributes/i })).toBeInTheDocument() + expect(attributeKey1.value).toBe("VONAME"); + expect(attributeKey1).toBeDisabled() + expect(attributeVal1.value).toBe('--vo'); + expect(attributeVal1).toBeDisabled() + expect(attributeKey2.value).toBe("X509_USER_PROXY") + expect(attributeKey2).toBeDisabled() + expect(attributeVal2.value).toBe("-x") + expect(attributeVal2).toBeDisabled() + expect(screen.queryByTestId('attributes.0.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('attributes.1.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('attributes.addnew')).not.toBeInTheDocument(); + + expect(screen.getByRole("heading", { name: /dependency/i })).toBeInTheDocument() + expect(dependencyKey.value).toBe("hr.srce.GridProxy-Get") + expect(dependencyKey).toBeDisabled() + expect(dependencyVal.value).toBe("0") + expect(dependencyVal).toBeDisabled() + expect(screen.queryByTestId('dependency.0.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('dependency.addnew')).not.toBeInTheDocument(); + + expect(screen.getByRole("heading", { name: /parameter/i })).toBeInTheDocument() + expect(parameterKey.value).toBe("") + expect(parameterKey).toBeDisabled() + expect(parameterVal.value).toBe("") + expect(parameterVal).toBeDisabled() + expect(screen.queryByTestId('parameter.0.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('parameter.addnew')).not.toBeInTheDocument(); + + expect(screen.getByRole("heading", { name: /flags/i })).toBeInTheDocument() + expect(flagKey1.value).toBe("NOHOSTNAME") + expect(flagKey1).toBeDisabled() + expect(flagVal1.value).toBe("1") + expect(flagVal1).toBeDisabled() + expect(flagKey2.value).toBe("VO") + expect(flagKey2).toBeDisabled() + expect(flagVal2.value).toBe("1") + expect(flagVal2).toBeDisabled() + expect(flagKey3.value).toBe("NOPUBLISH") + expect(flagKey3).toBeDisabled() + expect(flagVal3.value).toBe("1") + expect(flagVal3).toBeDisabled() + expect(screen.queryByTestId('flags.0.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('flags.1.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('flags.2.remove')).not.toBeInTheDocument(); + expect(screen.queryByTestId('flags.addnew')).not.toBeInTheDocument(); + + expect(screen.getByRole("heading", { name: /parent/i })).toBeInTheDocument() + expect(parentField.value).toBe(''); + expect(parentField).toBeDisabled() + + expect(screen.getByRole('button', { name: /history/i }).closest('a')).toHaveAttribute('href', '/ui/public_metrictemplates/srce.gridproxy.validity/history'); + expect(screen.queryByRole('button', { name: /clone/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument(); + }) + test("Test change main metric template info", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ ok: true, status: 200 }) @@ -1971,77 +2305,6 @@ describe('Test metric template changeview on SuperPOEM', () => { ) }) - /* - test("Test change dependency", async () => { - mockChangeObject.mockReturnValueOnce( - Promise.resolve({ ok: true, status: 200 }) - ) - - renderChangeView(); - - await waitFor(() => { - expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() - }) - - fireEvent.change(screen.getByTestId('dependency.0.key'), { target: { value: 'test.AMS-Check' } }); - fireEvent.change(screen.getByTestId('dependency.0.value'), { target: { value: '1' } }); - - fireEvent.click(screen.getByTestId("dependency.addnew")) - fireEvent.change(screen.getByTestId('dependency.1.key'), { target: { value: 'test2.AMS-Check' } }); - fireEvent.change(screen.getByTestId('dependency.1.value'), { target: { value: '0' } }); - - fireEvent.click(screen.getByRole('button', { name: /save/i })); - await waitFor(() => { - expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); - }) - fireEvent.click(screen.getByRole('button', { name: /yes/i })); - - await waitFor(() => { - expect(mockChangeObject).toHaveBeenCalledWith( - "/api/v2/internal/metrictemplates/", - { - id: "1", - name: "argo.AMS-Check", - mtype: "Active", - tags: ["test_tag1", "test_tag2"], - description: "Some description of argo.AMS-Check metric template.", - probeversion: "ams-probe (0.1.12)", - parent: "", - probeexecutable: "ams-probe", - config: [ - { key: "maxCheckAttempts", value: "4" }, - { key: "timeout", value: "70" }, - { key: "path", value: "/usr/libexec/argo-monitoring/" }, - { key: "interval", value: "5" }, - { key: "retryInterval", value: "3" } - ], - attribute: [ - { key: "argo.ams_TOKEN", value: "--token" } - ], - dependency: [ - { key: "test.AMS-Check", value: "1" }, - { key: "test2.AMS-Check", value: "0", isNew: true } - ], - parameter: [ - { key: "--project", value: "EGI" } - ], - flags: [ - { key: "OBSESS", value: "1" } - ], - files: [{ key: "", value: "" }], - fileparameter: [{ key: "", value: "" }] - } - ) - }) - - expect(queryClient.invalidateQueries).toHaveBeenCalledWith('metrictemplate'); - expect(queryClient.invalidateQueries).toHaveBeenCalledWith('public_metrictemplate'); - expect(NotificationManager.success).toHaveBeenCalledWith( - 'Metric template successfully changed', 'Changed', 2000 - ) - }) - */ - test("Test change parameter", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ ok: true, status: 200 }) @@ -3489,175 +3752,6 @@ describe('Test metric template addview on SuperPOEM', () => { }) }) - /* - test("Test add dependency", async () => { - renderAddView(); - - await waitFor(() => { - expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() - }) - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - - fireEvent.change(screen.getByTestId("dependency.0.key"), { target: { value: 'test.AMS-Check' } }); - fireEvent.change(screen.getByTestId("dependency.0.value"), { target: { value: '1' } }); - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "test.AMS-Check", - "dependency.0.value": "1", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - - fireEvent.click(screen.getByTestId("dependency.addnew")) - fireEvent.change(screen.getByTestId("dependency.1.key"), { target: { value: 'generic.http.connect' } }); - fireEvent.change(screen.getByTestId("dependency.1.value"), { target: { value: '0' } }); - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "test.AMS-Check", - "dependency.0.value": "1", - "dependency.1.key": "generic.http.connect", - "dependency.1.value": "0", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - - fireEvent.click(screen.getByTestId("dependency.0.remove")) - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "generic.http.connect", - "dependency.0.value": "0", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - - fireEvent.change(screen.getByTestId("dependency.0.value"), { target: { value: '1' } }); - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "generic.http.connect", - "dependency.0.value": "1", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - - fireEvent.click(screen.getByTestId("dependency.0.remove")) - - expect(screen.getByTestId("metric-form")).toHaveFormValues({ - name: "", - description: "", - probeexecutable: "", - "config.0.key": "maxCheckAttempts", - "config.0.value": "", - "config.1.key": "timeout", - "config.1.value": "", - "config.2.key": "path", - "config.2.value": "", - "config.3.key": "interval", - "config.3.value": "", - "config.4.key": "retryInterval", - "config.4.value": "", - "attributes.0.key": "", - "attributes.0.value": "", - "dependency.0.key": "", - "dependency.0.value": "", - "parameter.0.key": "", - "parameter.0.value": "", - "flags.0.key": "", - "flags.0.value": "" - }) - }) - */ - test("Test add parameter", async () => { renderAddView(); diff --git a/poem/Poem/frontend/react/__tests__/Metrics.test.js b/poem/Poem/frontend/react/__tests__/Metrics.test.js index 58ddbc507..f628cb473 100644 --- a/poem/Poem/frontend/react/__tests__/Metrics.test.js +++ b/poem/Poem/frontend/react/__tests__/Metrics.test.js @@ -123,9 +123,81 @@ const mockMetric = { parameter: [ { key: '--project', value: 'EGI' } ], + files: [], fileparameter: [] }; +const mockMetricWithDependency = { + id: 422, + name: "srce.gridproxy.validity", + mtype: "Active", + tags: [ + "test_tag1", + "test_tag2", + "internal" + ], + probeversion: "GridProxy-probe (0.2.0)", + group: "EGI", + description: "", + parent: "", + probeexecutable: "GridProxy-probe", + config: [ + { + key: "maxCheckAttempts", + value: "3" + }, + { + key: "timeout", + value: "30" + }, + { + key: "path", + value: "/usr/libexec/argo/probes/globus" + }, + { + key: "interval", + value: "15" + }, + { + key: "retryInterval", + value: "3" + } + ], + attribute: [ + { + key: "VONAME", + value: "--vo" + }, + { + key: "X509_USER_PROXY", + value: "-x" + } + ], + dependancy: [ + { + key: "hr.srce.GridProxy-Get", + value: "0" + } + ], + flags: [ + { + key: "NOHOSTNAME", + value: "1" + }, + { + key: "VO", + value: "1" + }, + { + key: "NOPUBLISH", + value: "1" + } + ], + files: [], + parameter: [], + fileparameter: [] +} + const mockPassiveMetric = { id: 2, name: 'org.apel.APEL-Pub', @@ -167,6 +239,24 @@ const mockProbe = [{ version: '0.1.12' }]; +const mockProbe2 = [{ + id: '64', + object_repr: "GridProxy-probe (0.2.0)", + fields: { + name: "GridProxy-probe", + version: "0.2.0", + package: "argo-probe-globus (0.2.0)", + description: "Probe for functional checking of MyProxy service.", + comment: "Harmonized version.", + repository: "https://github.com/ARGOeu-Metrics/argo-probe-globus", + docurl: "https://github.com/ARGOeu-Metrics/argo-probe-globus/blob/master/README.md" + }, + user: "poem", + date_created: "2019-12-09 09:24:20", + comment: "Initial version.", + version: "0.2.0" +}] + const mockUserGroups = { 'aggregations': ['EGI'], 'metrics': ['EGI', 'ARGOTEST'], @@ -704,17 +794,29 @@ describe('Tests for metric change', () => { case '/api/v2/internal/metric/org.apel.APEL-Pub': return Promise.resolve(mockPassiveMetric) + case "/api/v2/internal/metric/srce.gridproxy.validity": + return Promise.resolve(mockMetricWithDependency) + case '/api/v2/internal/public_metric/argo.AMS-Check': return Promise.resolve(mockMetric) case '/api/v2/internal/public_metric/org.apel.APEL-Pub': return Promise.resolve(mockPassiveMetric) + case "/api/v2/internal/public_metric/srce.gridproxy.validity": + return Promise.resolve(mockMetricWithDependency) + case '/api/v2/internal/version/probe/ams-probe': return Promise.resolve(mockProbe) + + case "/api/v2/internal/version/probe/GridProxy-probe": + return Promise.resolve(mockProbe2) case '/api/v2/internal/public_version/probe/ams-probe': return Promise.resolve(mockProbe) + + case "/api/v2/internal/public_version/probe/GridProxy-probe": + return Promise.resolve(mockProbe2) } }, isActiveSession: () => Promise.resolve(mockActiveSession), @@ -845,6 +947,159 @@ describe('Tests for metric change', () => { expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument(); }) + test('Test that page renders properly if metric with dependency', async () => { + renderChangeView({ route: "/ui/metrics/srce.gridproxy.validity" }) + + await waitFor(() => { + expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument(); + }) + + expect(screen.getByRole('heading', { name: /change metric/i }).textContent).toBe('Change metric'); + + const nameField = screen.getByTestId('name'); + expect(nameField.value).toBe("srce.gridproxy.validity") + expect(nameField).toBeDisabled() + + const typeField = screen.getByTestId('mtype'); + expect(typeField.value).toBe('Active'); + expect(typeField).toBeDisabled() + + const probeField = screen.getByTestId('probeversion'); + expect(probeField.value).toBe('GridProxy-probe (0.2.0)'); + expect(probeField).toBeDisabled(); + + const packageField = screen.getByTestId('package'); + expect(packageField.value).toBe('argo-probe-globus (0.2.0)'); + expect(packageField).toBeDisabled(); + + expect(screen.queryAllByText(/test_tag/i)).toHaveLength(2); + expect(screen.getByText("internal")).toBeInTheDocument() + + const descriptionField = screen.getByTestId('description'); + expect(descriptionField.value).toBe("") + expect(descriptionField).toBeDisabled(); + + const groupField = screen.getByText('EGI'); + expect(groupField).toBeEnabled() + + expect(screen.queryByText('ARGOTEST')).not.toBeInTheDocument() + selectEvent.openMenu(groupField) + expect(screen.getByText('ARGOTEST')).toBeInTheDocument() + + expect(screen.getByRole('heading', { name: /metric configuration/i })).toBeInTheDocument() + + expect(screen.getByRole('heading', { name: /executable/i }).textContent).toBe('probe executable'); + const executableField = screen.getByTestId('probeexecutable'); + expect(executableField.value).toBe('GridProxy-probe'); + expect(executableField).toBeDisabled() + + const configKey1 = screen.getByTestId('config.0.key'); + expect(configKey1.value).toBe('maxCheckAttempts'); + expect(configKey1).toBeDisabled() + + const configKey2 = screen.getByTestId('config.1.key'); + expect(configKey2.value).toBe('timeout'); + expect(configKey2).toBeDisabled() + + const configKey3 = screen.getByTestId('config.2.key'); + expect(configKey3.value).toBe('path'); + expect(configKey3).toBeDisabled() + + const configKey4 = screen.getByTestId('config.3.key'); + expect(configKey4.value).toBe('interval'); + expect(configKey4).toBeDisabled() + + const configKey5 = screen.getByTestId('config.4.key'); + expect(configKey5.value).toBe('retryInterval'); + expect(configKey5).toBeDisabled() + + const configVal1 = screen.getByTestId('config.0.value'); + expect(configVal1.value).toBe('3'); + expect(configVal1).not.toBeDisabled() + + const configVal2 = screen.getByTestId('config.1.value'); + expect(configVal2.value).toBe('30'); + expect(configVal2).not.toBeDisabled() + + const configVal3 = screen.getByTestId('config.2.value'); + expect(configVal3.value).toBe("/usr/libexec/argo/probes/globus") + expect(configVal3).toBeDisabled() + + const configVal4 = screen.getByTestId('config.3.value'); + expect(configVal4.value).toBe('15') + expect(configVal4).not.toBeDisabled() + + const configVal5 = screen.getByTestId('config.4.value'); + expect(configVal5.value).toBe("3") + expect(configVal5).not.toBeDisabled() + + const attributeKey1 = screen.getByTestId('attributes.0.key'); + expect(attributeKey1.value).toBe("VONAME"); + expect(attributeKey1).toBeDisabled() + + const attributeVal1 = screen.getByTestId('attributes.0.value') + expect(attributeVal1.value).toBe('--vo'); + expect(attributeVal1).toBeDisabled() + + const attributeKey2 = screen.getByTestId("attributes.1.key") + expect(attributeKey2.value).toBe("X509_USER_PROXY") + expect(attributeKey2).toBeDisabled() + + const attributeVal2 = screen.getByTestId("attributes.1.value") + expect(attributeVal2.value).toBe("-x") + expect(attributeVal2).toBeDisabled() + + expect(screen.queryByTestId("attributes.2.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("attributes.2.value")).not.toBeInTheDocument() + + const dependencyKey = screen.getByTestId("dependency.0.key") + expect(dependencyKey.value).toBe("hr.srce.GridProxy-Get") + expect(dependencyKey).toBeDisabled() + + const dependencyVal= screen.getByTestId("dependency.0.value") + expect(dependencyVal.value).toBe("0") + expect(dependencyVal).toBeDisabled() + + const parameterKey = screen.getByTestId("parameter.0.key") + expect(parameterKey.value).toBe("") + expect(parameterKey).toBeDisabled() + + const parameterVal = screen.getByTestId("parameter.0.value") + expect(parameterVal.value).toBe("") + expect(parameterVal).toBeDisabled() + + const flagKey1 = screen.getByTestId('flags.0.key'); + expect(flagKey1.value).toBe('NOHOSTNAME'); + expect(flagKey1).toBeDisabled() + + const flagVal1 = screen.getByTestId('flags.0.value'); + expect(flagVal1.value).toBe('1'); + expect(flagVal1).toBeDisabled() + + const flagKey2 = screen.getByTestId("flags.1.key") + expect(flagKey2.value).toBe("VO") + expect(flagKey2).toBeDisabled() + + const flagVal2 = screen.getByTestId("flags.1.value") + expect(flagVal2.value).toBe("1") + + const flagKey3 = screen.getByTestId("flags.2.key") + expect(flagKey3.value).toBe("NOPUBLISH") + expect(flagKey3).toBeDisabled() + + const flagVal3 = screen.getByTestId("flags.2.value") + expect(flagVal3.value).toBe("1") + expect(flagVal3).toBeDisabled() + + const parentField = screen.getByTestId('parent'); + expect(parentField.value).toBe(''); + expect(parentField).toBeDisabled() + + expect(screen.getByRole('button', { name: /history/i }).closest('a')).toHaveAttribute('href', '/ui/metrics/srce.gridproxy.validity/history') + expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument(); + }) + test('Test that public page renders properly', async () => { Backend.mockImplementationOnce(() => { return { @@ -985,6 +1240,174 @@ describe('Tests for metric change', () => { expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() }) + test('Test that public page renders properly if metric with dependency', async () => { + Backend.mockImplementationOnce(() => { + return { + fetchData: (path) => { + switch (path) { + case '/api/v2/internal/public_metric/srce.gridproxy.validity': + return Promise.resolve(mockMetricWithDependency) + + case '/api/v2/internal/public_version/probe/GridProxy-probe': + return Promise.resolve(mockProbe2) + } + }, + isActiveSession: () => Promise.resolve({ + json: () => Promise.resolve({ detail: 'Authentication credentials were not provided.'}), + status: 403, + statusText: 'FORBIDDEN' + }) + } + }) + renderChangeView({ publicView: true, route: "/ui/public_metrics/srce.gridproxy.validity" }) + + await waitFor(() => { + expect(screen.getByTestId("config.0.key")).toBeInTheDocument() + }) + + expect(screen.getByRole('heading', { name: /details/i }).textContent).toBe('Metric details'); + + const nameField = screen.getByTestId('name'); + expect(nameField.value).toBe("srce.gridproxy.validity") + expect(nameField).toBeDisabled() + + const typeField = screen.getByTestId('mtype'); + expect(typeField.value).toBe('Active'); + expect(typeField).toBeDisabled() + + const probeField = screen.getByTestId('probeversion'); + expect(probeField.value).toBe('GridProxy-probe (0.2.0)'); + expect(probeField).toBeDisabled(); + + const packageField = screen.getByTestId('package'); + expect(packageField.value).toBe('argo-probe-globus (0.2.0)'); + expect(packageField).toBeDisabled(); + + expect(screen.queryAllByText(/test_tag/i)).toHaveLength(2); + expect(screen.getByText("internal")).toBeInTheDocument() + + const descriptionField = screen.getByTestId('description'); + expect(descriptionField.value).toBe("") + expect(descriptionField).toBeDisabled(); + + const groupField = screen.getByTestId("group") + expect(groupField.value).toBe("EGI") + expect(groupField).toBeDisabled() + + expect(screen.getByRole('heading', { name: /metric configuration/i })).toBeInTheDocument() + + expect(screen.getByRole('heading', { name: /executable/i }).textContent).toBe('probe executable'); + const executableField = screen.getByTestId('probeexecutable'); + expect(executableField.value).toBe('GridProxy-probe'); + expect(executableField).toBeDisabled() + + const configKey1 = screen.getByTestId('config.0.key'); + expect(configKey1.value).toBe('maxCheckAttempts'); + expect(configKey1).toBeDisabled() + + const configKey2 = screen.getByTestId('config.1.key'); + expect(configKey2.value).toBe('timeout'); + expect(configKey2).toBeDisabled() + + const configKey3 = screen.getByTestId('config.2.key'); + expect(configKey3.value).toBe('path'); + expect(configKey3).toBeDisabled() + + const configKey4 = screen.getByTestId('config.3.key'); + expect(configKey4.value).toBe('interval'); + expect(configKey4).toBeDisabled() + + const configKey5 = screen.getByTestId('config.4.key'); + expect(configKey5.value).toBe('retryInterval'); + expect(configKey5).toBeDisabled() + + const configVal1 = screen.getByTestId('config.0.value'); + expect(configVal1.value).toBe('3'); + expect(configVal1).toBeDisabled() + + const configVal2 = screen.getByTestId('config.1.value'); + expect(configVal2.value).toBe('30'); + expect(configVal2).toBeDisabled() + + const configVal3 = screen.getByTestId('config.2.value'); + expect(configVal3.value).toBe("/usr/libexec/argo/probes/globus") + expect(configVal3).toBeDisabled() + + const configVal4 = screen.getByTestId('config.3.value'); + expect(configVal4.value).toBe('15') + expect(configVal4).toBeDisabled() + + const configVal5 = screen.getByTestId('config.4.value'); + expect(configVal5.value).toBe("3") + expect(configVal5).toBeDisabled() + + const attributeKey1 = screen.getByTestId('attributes.0.key'); + expect(attributeKey1.value).toBe("VONAME"); + expect(attributeKey1).toBeDisabled() + + const attributeVal1 = screen.getByTestId('attributes.0.value') + expect(attributeVal1.value).toBe('--vo'); + expect(attributeVal1).toBeDisabled() + + const attributeKey2 = screen.getByTestId("attributes.1.key") + expect(attributeKey2.value).toBe("X509_USER_PROXY") + expect(attributeKey2).toBeDisabled() + + const attributeVal2 = screen.getByTestId("attributes.1.value") + expect(attributeVal2.value).toBe("-x") + expect(attributeVal2).toBeDisabled() + + expect(screen.queryByTestId("attributes.2.key")).not.toBeInTheDocument() + expect(screen.queryByTestId("attributes.2.value")).not.toBeInTheDocument() + + const dependencyKey = screen.getByTestId("dependency.0.key") + expect(dependencyKey.value).toBe("hr.srce.GridProxy-Get") + expect(dependencyKey).toBeDisabled() + + const dependencyVal= screen.getByTestId("dependency.0.value") + expect(dependencyVal.value).toBe("0") + expect(dependencyVal).toBeDisabled() + + const parameterKey = screen.getByTestId("parameter.0.key") + expect(parameterKey.value).toBe("") + expect(parameterKey).toBeDisabled() + + const parameterVal = screen.getByTestId("parameter.0.value") + expect(parameterVal.value).toBe("") + expect(parameterVal).toBeDisabled() + + const flagKey1 = screen.getByTestId('flags.0.key'); + expect(flagKey1.value).toBe('NOHOSTNAME'); + expect(flagKey1).toBeDisabled() + + const flagVal1 = screen.getByTestId('flags.0.value'); + expect(flagVal1.value).toBe('1'); + expect(flagVal1).toBeDisabled() + + const flagKey2 = screen.getByTestId("flags.1.key") + expect(flagKey2.value).toBe("VO") + expect(flagKey2).toBeDisabled() + + const flagVal2 = screen.getByTestId("flags.1.value") + expect(flagVal2.value).toBe("1") + + const flagKey3 = screen.getByTestId("flags.2.key") + expect(flagKey3.value).toBe("NOPUBLISH") + expect(flagKey3).toBeDisabled() + + const flagVal3 = screen.getByTestId("flags.2.value") + expect(flagVal3.value).toBe("1") + expect(flagVal3).toBeDisabled() + + const parentField = screen.getByTestId('parent'); + expect(parentField.value).toBe(''); + expect(parentField).toBeDisabled() + + expect(screen.queryByRole('button', { name: /history/i })).not.toBeInTheDocument() + expect(screen.queryByRole("button", { name: /clone/i })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument() + }) + test('Test that passive metric changeview renders properly', async () => { renderChangeView({ route: '/ui/metrics/org.apel.APEL-Pub' }); @@ -1327,7 +1750,6 @@ describe('Tests for metric change', () => { attribute: mockMetric.attribute, dependancy: mockMetric.dependancy, flags: mockMetric.flags, - files: mockMetric.files, parameter: mockMetric.parameter } ) From 89a41c74b0ec03aec1fe9cd999f8c445e8b8977f Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Wed, 26 Jun 2024 14:15:43 +0200 Subject: [PATCH 22/22] Leave out placeholder for dependency field for metric form --- poem/Poem/frontend/react/Placeholders.js | 1 - 1 file changed, 1 deletion(-) diff --git a/poem/Poem/frontend/react/Placeholders.js b/poem/Poem/frontend/react/Placeholders.js index 6bef27f69..0dfdfd94f 100644 --- a/poem/Poem/frontend/react/Placeholders.js +++ b/poem/Poem/frontend/react/Placeholders.js @@ -288,7 +288,6 @@ export const MetricFormPlaceholder = ( props ) => { -
parent
@@ -171,32 +190,31 @@ const MetricsList = () => { { context.publicView ? - item + item.name : { - let tmpMetrics = getValues("metrics4tag") - let origIndex = tmpMetrics.findIndex(e => e == item) - tmpMetrics[origIndex] = e.value - setValue("metrics4tag", tmpMetrics) + let origIndex = getValues("metrics4tag").findIndex(met => met.name == getValues(`view_metrics4tag.${index}.name`) ) + setValue(`metrics4tag.${origIndex}.name`, e.value) + setValue(`view_metrics4tag.${index}.name`, e.value) } } options={ context.allMetrics.map( met => met.name ).filter( - met => !context.metrics4tag.includes(met) + met => !context.metrics4tag.map(met => met.name).includes(met) ).map( option => new Object({ label: option, value: option }) )} - value={ { label: item, value: item } } + value={ { label: field.value, value: field.value } } /> } /> @@ -209,14 +227,7 @@ const MetricsList = () => { size="sm" color="light" data-testid={`remove-${index}`} - onClick={() => { - let tmpMetrics = context.metrics4tag - let origIndex = tmpMetrics.findIndex(e => e == item) - tmpMetrics.splice(origIndex, 1) - if (tmpMetrics.length === 0) - tmpMetrics = [""] - setValue("metrics4tag", tmpMetrics) - }} + onClick={() => onRemove(index)} > @@ -224,11 +235,7 @@ const MetricsList = () => { size="sm" color="light" data-testid={`insert-${index}`} - onClick={() => { - let tmpMetrics = context.metrics4tag - tmpMetrics.splice(index + 1, 0, "") - setValue("metrics4tag", tmpMetrics) - }} + onClick={() => onInsert(index)} > @@ -266,11 +273,14 @@ const MetricTagsForm = ({ const addMutation = useMutation(async (values) => await backend.addObject('/api/v2/internal/metrictags/', values)); const deleteMutation = useMutation(async () => await backend.deleteObject(`/api/v2/internal/metrictags/${name}`)) + const default_metrics4tag = tag?.metrics.length > 0 ? tag.metrics : [{ name: "" }] + const methods = useForm({ defaultValues: { id: `${tag ? tag.id : ""}`, name: `${tag ? tag.name : ""}`, - metrics4tag: tag?.metrics.length > 0 ? tag.metrics : [""], + metrics4tag: default_metrics4tag, + view_metrics4tag: default_metrics4tag, searchItem: "" }, mode: "all", @@ -281,6 +291,23 @@ const MetricTagsForm = ({ const searchItem = useWatch({ control, name: "searchItem" }) const metrics4tag = useWatch({ control, name: "metrics4tag" }) + const view_metrics4tag = useWatch({ control, name: "view_metrics4tag" }) + + useEffect(() => { + if (view_metrics4tag.length === 0) { + methods.setValue("view_metrics4tag", [{ name: "" }]) + } + }, [view_metrics4tag]) + + useEffect(() => { + if (metrics4tag.length === 0) { + methods.setValue("metrics4tag", [{ name: "" }]) + } + }, [metrics4tag]) + + useEffect(() => { + methods.setValue("view_metrics4tag", metrics4tag.filter(e => e.name.toLowerCase().includes(searchItem.toLowerCase()))) + }, [searchItem]) const toggleAreYouSure = () => { setAreYouSureModal(!areYouSureModal) @@ -301,7 +328,7 @@ const MetricTagsForm = ({ const sendValues = new Object({ name: formValues.name, - metrics: formValues.metrics4tag.filter(met => met !== "") + metrics: formValues.metrics4tag.map(met => met.name).filter(met => met !== "") }) if (addview) diff --git a/poem/Poem/frontend/react/__tests__/MetricTags.test.js b/poem/Poem/frontend/react/__tests__/MetricTags.test.js index 2a8f82e67..74d99af23 100644 --- a/poem/Poem/frontend/react/__tests__/MetricTags.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricTags.test.js @@ -45,22 +45,27 @@ const mockListTags = [ { id: "3", name: "internal", - metrics: ["argo.AMSPublisher-Check"] + metrics: [ + { "name": "argo.AMSPublisher-Check" } + ] }, { id: "4", name: "harmonized", metrics: [ - "generic.certificate.validity", - "generic.http.connect", - "generic.tcp.connect", + { "name": "generic.certificate.validity" }, + { "name": "generic.http.connect" }, + { "name": "generic.tcp.connect" } ] }, { id: "2", name: "messaging", - metrics: ["argo.AMS-Check", "argo.AMSPublisher-Check"] - }, + metrics: [ + { "name": "argo.AMS-Check" }, + { "name": "argo.AMSPublisher-Check" } + ] + } ] const mockActiveSession = { @@ -697,15 +702,9 @@ describe("Test metric tags changeview", () => { expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() }) - await selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") - - fireEvent.click(screen.getByTestId("insert-0")) - - const table = within(screen.getByRole("table")) - - const row2 = table.getAllByRole("row")[3] - - await selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + await waitFor(() => { + selectEvent.select(screen.getByText("generic.certificate.validity"), "argo.AMS-Check") + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { @@ -716,7 +715,7 @@ describe("Test metric tags changeview", () => { await waitFor(() => { expect(mockChangeObject).toHaveBeenCalledWith( "/api/v2/internal/metrictags/", - { id: "4", name: "harmonized", metrics: ["argo.AMS-Check", "argo.AMSPublisher-Check", "generic.http.connect", "generic.tcp.connect"] } + { id: "4", name: "harmonized", metrics: ["argo.AMS-Check", "generic.http.connect", "generic.tcp.connect"] } ) }) @@ -846,6 +845,96 @@ describe("Test metric tags changeview", () => { ) }) + test("Test insert new metrics for metric tag", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByTestId("insert-0")) + + const table = within(screen.getByRole("table")) + + const row2 = table.getAllByRole("row")[3] + + await selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "harmonized", metrics: ["generic.certificate.validity", "argo.AMSPublisher-Check", "generic.http.connect", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + + test("Test insert new metrics for metric tag in filtered view", async () => { + mockChangeObject.mockReturnValueOnce( + Promise.resolve({ ok: true, status: 201, statusText: "CREATED" }) + ) + + renderChangeView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: "connect" } }) + + fireEvent.click(screen.getByTestId("insert-0")) + + const table = within(screen.getByRole("table")) + + const row2 = table.getAllByRole("row")[3] + + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + }) + + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /change/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockChangeObject).toHaveBeenCalledWith( + "/api/v2/internal/metrictags/", + { id: "4", name: "harmonized", metrics: ["generic.certificate.validity", "generic.http.connect", "argo.AMSPublisher-Check", "generic.tcp.connect"] } + ) + }) + + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("metrictemplate") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictags") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metric") + expect(queryClient.invalidateQueries).toHaveBeenCalledWith("public_metrictemplate") + expect(NotificationManager.success).toHaveBeenCalledWith( + "Metric tag successfully changed", "Changed", 2000 + ) + }) + test("Test display warning messages", async () => { mockChangeObject.mockReturnValueOnce( Promise.resolve({ @@ -872,7 +961,9 @@ describe("Test metric tags changeview", () => { const row2 = table.getAllByRole("row")[3] - await selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { @@ -933,7 +1024,9 @@ describe("Test metric tags changeview", () => { const row2 = table.getAllByRole("row")[3] - await selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "argo.AMSPublisher-Check") + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { @@ -1174,6 +1267,11 @@ describe("Test metric tags addview", () => { expect(table.queryByText(/generic/i)).not.toBeInTheDocument() expect(table.queryByText(/argo/i)).not.toBeInTheDocument() + expect(table.queryByText("generic.certificate.validity")).not.toBeInTheDocument() + expect(table.queryByText("generic.http.connect")).not.toBeInTheDocument() + expect(table.queryByText("generic.tcp.connect")).not.toBeInTheDocument() + expect(table.queryByText("argo.AMS-Check")).not.toBeInTheDocument() + expect(table.queryByText("argo.AMSPublisher-Check")).not.toBeInTheDocument() selectEvent.openMenu(input) expect(table.getByText("generic.certificate.validity")).toBeInTheDocument() expect(table.getByText("generic.http.connect")).toBeInTheDocument() @@ -1250,24 +1348,46 @@ describe("Test metric tags addview", () => { const row1 = table.getAllByRole("row")[2] const input1 = within(row1).getByRole("combobox") - await selectEvent.select(input1, "generic.certificate.validity") + await waitFor(() => { + selectEvent.select(input1, "generic.certificate.validity") + }) + + await waitFor(() => { + expect(table.queryByText("generic.http.connect")).not.toBeInTheDocument() + }) fireEvent.click(table.getByTestId("insert-0")) const row2 = table.getAllByRole("row")[3] const input2 = within(row2).getByRole("combobox") - await selectEvent.select(input2, "generic.http.connect") + await waitFor(() => { + selectEvent.select(input2, "generic.http.connect") + }) + + await waitFor(() => { + expect(table.queryByText("generic.tcp.connect")).not.toBeInTheDocument() + }) fireEvent.click(table.getByTestId("insert-1")) const row3 = table.getAllByRole("row")[4] const input3 = within(row3).getByRole("combobox") - await selectEvent.select(input3, "generic.tcp.connect") + await waitFor(() => { + selectEvent.select(input3, "generic.tcp.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.tcp.connect")).toBeInTheDocument() + }) fireEvent.click(table.getByTestId("remove-1")) + await waitFor(() => { + expect(table.queryByText("generic.http.connect")).not.toBeInTheDocument() + }) + await selectEvent.select(table.getByText("generic.tcp.connect"), "argo.AMS-Check") fireEvent.click(screen.getByRole('button', { name: /save/i })); @@ -1317,13 +1437,25 @@ describe("Test metric tags addview", () => { const row1 = table.getAllByRole("row")[2] const input1 = within(row1).getByRole("combobox") - await selectEvent.select(input1, "generic.tcp.connect") + await waitFor(() => { + selectEvent.select(input1, "generic.tcp.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.tcp.connect")).toBeInTheDocument() + }) fireEvent.click(table.getByTestId("insert-0")) const row2 = table.getAllByRole("row")[3] - await selectEvent.select(within(row2).getByRole("combobox"), "generic.http.connect") + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "generic.http.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.http.connect")).toBeInTheDocument() + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { @@ -1381,13 +1513,25 @@ describe("Test metric tags addview", () => { const row1 = table.getAllByRole("row")[2] const input1 = within(row1).getByRole("combobox") - await selectEvent.select(input1, "generic.tcp.connect") + await waitFor(() => { + selectEvent.select(input1, "generic.tcp.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.tcp.connect")).toBeInTheDocument() + }) fireEvent.click(table.getByTestId("insert-0")) const row2 = table.getAllByRole("row")[3] - await selectEvent.select(within(row2).getByRole("combobox"), "generic.http.connect") + await waitFor(() => { + selectEvent.select(within(row2).getByRole("combobox"), "generic.http.connect") + }) + + await waitFor(() => { + expect(table.getByText("generic.http.connect")).toBeInTheDocument() + }) fireEvent.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { From 730c18834353854b32f734641ac5c187f2113a79 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Mon, 17 Jun 2024 14:17:26 +0200 Subject: [PATCH 08/22] When importing internal metrics, leave out the ones that have reached their EOL --- .../poem/management/commands/import_internal_metrics.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/poem/Poem/poem/management/commands/import_internal_metrics.py b/poem/Poem/poem/management/commands/import_internal_metrics.py index cea27f5e0..6b4b3ef4b 100644 --- a/poem/Poem/poem/management/commands/import_internal_metrics.py +++ b/poem/Poem/poem/management/commands/import_internal_metrics.py @@ -15,7 +15,8 @@ def handle(self, *args, **kwargs): tenant = connection.tenant internal_metrics = [ mt.name for mt in admin_models.MetricTemplate.objects.all() - if 'internal' in [tag.name for tag in mt.tags.all()] + if 'internal' in [tag.name for tag in mt.tags.all()] and + "eol" not in [tag.name for tag in mt.tags.all()] ] if len(internal_metrics) > 0: try: @@ -32,8 +33,8 @@ def handle(self, *args, **kwargs): get_user_model().DoesNotExist, NoSectionError, NoOptionError ): self.stderr.write( - 'Super user for tenant {} is not defined.\n' - 'Internal metrics not imported.'.format(tenant.name) + f'Super user for tenant {tenant.name} is not defined.\n' + f'Internal metrics not imported.' ) else: From 0923af9e60f4e6ccb7eac26e6215c989877c7ff4 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Mon, 17 Jun 2024 15:41:27 +0200 Subject: [PATCH 09/22] Treat internal metrics same as all the others when syncing metrics --- poem/Poem/api/tests/test_helpers.py | 36 ++++++++++++++++++---------- poem/Poem/helpers/metrics_helpers.py | 22 +++++++---------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/poem/Poem/api/tests/test_helpers.py b/poem/Poem/api/tests/test_helpers.py index 87b89332f..9ba84e53b 100644 --- a/poem/Poem/api/tests/test_helpers.py +++ b/poem/Poem/api/tests/test_helpers.py @@ -3587,10 +3587,13 @@ def test_sync_active_metrics(self, mock_get_metrics): self.assertEqual(err, []) self.assertEqual(unavailable, []) self.assertEqual( - sorted(deleted), - ["eu.egi.cloud.OpenStack-Swift", "test.Metric-Template"] + sorted(deleted), [ + "argo.poem-tools.check", + "eu.egi.cloud.OpenStack-Swift", + "test.Metric-Template" + ] ) - self.assertEqual(len(poem_models.Metric.objects.all()), 5) + self.assertEqual(len(poem_models.Metric.objects.all()), 4) self.assertRaises( poem_models.Metric.DoesNotExist, poem_models.Metric.objects.get, @@ -3635,10 +3638,13 @@ def test_sync_passive_metrics(self, mock_get_metrics): self.assertEqual(err, []) self.assertEqual(unavailable, []) self.assertEqual( - sorted(deleted), - ["eu.egi.cloud.OpenStack-Swift", "test.Metric-Template"] + sorted(deleted), [ + "argo.poem-tools.check", + "eu.egi.cloud.OpenStack-Swift", + "test.Metric-Template" + ] ) - self.assertEqual(len(poem_models.Metric.objects.all()), 5) + self.assertEqual(len(poem_models.Metric.objects.all()), 4) self.assertRaises( poem_models.Metric.DoesNotExist, poem_models.Metric.objects.get, @@ -3683,10 +3689,13 @@ def test_sync_metrics_with_warning(self, mock_get_metrics): self.assertEqual(err, []) self.assertEqual(unavailable, []) self.assertEqual( - sorted(deleted), - ["eu.egi.cloud.OpenStack-Swift", "test.Metric-Template"] + sorted(deleted), [ + "argo.poem-tools.check", + "eu.egi.cloud.OpenStack-Swift", + "test.Metric-Template" + ] ) - self.assertEqual(len(poem_models.Metric.objects.all()), 5) + self.assertEqual(len(poem_models.Metric.objects.all()), 4) self.assertRaises( poem_models.Metric.DoesNotExist, poem_models.Metric.objects.get, @@ -3731,10 +3740,13 @@ def test_sync_metrics_if_version_unavailable(self, mock_get_metrics): self.assertEqual(err, []) self.assertEqual(unavailable, ["test.MetricTemplate"]) self.assertEqual( - sorted(deleted), - ["eu.egi.cloud.OpenStack-Swift", "test.Metric-Template"] + sorted(deleted), [ + "argo.poem-tools.check", + "eu.egi.cloud.OpenStack-Swift", + "test.Metric-Template" + ] ) - self.assertEqual(len(poem_models.Metric.objects.all()), 4) + self.assertEqual(len(poem_models.Metric.objects.all()), 3) self.assertRaises( poem_models.Metric.DoesNotExist, poem_models.Metric.objects.get, diff --git a/poem/Poem/helpers/metrics_helpers.py b/poem/Poem/helpers/metrics_helpers.py index c350fe4a3..d8bb25a30 100644 --- a/poem/Poem/helpers/metrics_helpers.py +++ b/poem/Poem/helpers/metrics_helpers.py @@ -386,9 +386,6 @@ def sync_metrics(tenant, user): key for key in get_metrics_in_profiles(tenant=tenant) ] metrics = poem_models.Metric.objects.all().values_list("name", flat=True) - internal = admin_models.MetricTemplate.objects.filter( - tags__name="internal" - ).values_list("name", flat=True) missing_metrics = list(set(metrics_in_profiles).difference(set(metrics))) extra_metrics = list(set(metrics).difference(set(metrics_in_profiles))) @@ -399,15 +396,14 @@ def sync_metrics(tenant, user): deleted = list() for metric in extra_metrics: - if metric not in internal: - m = poem_models.Metric.objects.get(name=metric) - poem_models.TenantHistory.objects.filter( - object_id=m.id, - content_type=ContentType.objects.get_for_model( - poem_models.Metric - ) - ).delete() - m.delete() - deleted.append(metric) + m = poem_models.Metric.objects.get(name=metric) + poem_models.TenantHistory.objects.filter( + object_id=m.id, + content_type=ContentType.objects.get_for_model( + poem_models.Metric + ) + ).delete() + m.delete() + deleted.append(metric) return imported, warn, err, unavailable, deleted From fefe7883e00d3dce5c62ea633e9055a03a1d6aab Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 08:45:53 +0200 Subject: [PATCH 10/22] Fix handling of state in list of metric templates --- poem/Poem/frontend/react/Metrics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/poem/Poem/frontend/react/Metrics.js b/poem/Poem/frontend/react/Metrics.js index 7be3234f1..9132ed9a1 100644 --- a/poem/Poem/frontend/react/Metrics.js +++ b/poem/Poem/frontend/react/Metrics.js @@ -398,6 +398,7 @@ export const ListOfMetrics = (props) => { NotifyWarn({ msg: data.warning, title: 'Deleted' }) setSelectAll(0); + setSelected({}); queryClient.invalidateQueries('metrictemplate'); queryClient.invalidateQueries('public_metrictemplate'); }, From 3a0b9c3cbcdec4967523b32738a4ace934e0648c Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 10:49:25 +0200 Subject: [PATCH 11/22] Add import .csv button in metric profile addview --- poem/Poem/frontend/react/MetricProfiles.js | 2 +- .../react/__tests__/MetricProfiles.test.js | 248 +++++++++++++++++- 2 files changed, 247 insertions(+), 3 deletions(-) diff --git a/poem/Poem/frontend/react/MetricProfiles.js b/poem/Poem/frontend/react/MetricProfiles.js index 1aac5429c..65168151a 100644 --- a/poem/Poem/frontend/react/MetricProfiles.js +++ b/poem/Poem/frontend/react/MetricProfiles.js @@ -481,7 +481,7 @@ const MetricProfilesForm = ({ infoview={historyview} submitperm={write_perm} extra_button={ - !addview && + !publicView && setDropdownOpen(!dropdownOpen)}> CSV diff --git a/poem/Poem/frontend/react/__tests__/MetricProfiles.test.js b/poem/Poem/frontend/react/__tests__/MetricProfiles.test.js index a34e8c080..3c07cf974 100644 --- a/poem/Poem/frontend/react/__tests__/MetricProfiles.test.js +++ b/poem/Poem/frontend/react/__tests__/MetricProfiles.test.js @@ -1466,6 +1466,7 @@ describe('Tests for metric profiles changeview', () => { expect(metricInstances.getAllByTestId(/insert-/i)).toHaveLength(2); expect(screen.queryByText('Must be one of predefined metrics')).not.toBeInTheDocument() + }) test('Test import csv with nonexisting metrics', async () => { @@ -3549,7 +3550,7 @@ describe('Tests for metric profile addview', () => { expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /clone/i })).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: /csv/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /csv/i })).toBeInTheDocument(); }) test('Test that page renders properly for combined tenant', async () => { @@ -3598,7 +3599,7 @@ describe('Tests for metric profile addview', () => { expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /clone/i })).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: /csv/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /csv/i })).toBeInTheDocument(); }) test("Test add main profile info", async () => { @@ -3745,6 +3746,249 @@ describe('Tests for metric profile addview', () => { expect(row1.getAllByText("Select...")).toHaveLength(2) }) + test("Test import csv successfully", async () => { + mockAddMetricProfile.mockReturnValueOnce( + Promise.resolve({ + status: { + message: 'Metric profile Created', + code: '200' + }, + data: { + id: 'va0ahsh6-6rs0-14ho-xlh9-wahso4hie7iv', + links: { + self: 'string' + } + } + }) + ) + + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByTestId('name'), { target: { value: 'NEW_PROFILE' } }); + fireEvent.change(screen.getByLabelText(/description/i), { target: { value: 'New central ARGO_MON profile.' } }); + + await waitFor(() => { + selectEvent.select(screen.getAllByText("Select...")[0], 'ARGO') + }) + + fireEvent.click(screen.getByRole('button', { name: /csv/i })); + fireEvent.click(screen.getByRole('menuitem', { name: /import/i })); + + const csv = 'service,metric\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-JobState\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-JobSubmit\r\n'; + + const content = new Blob([csv], { type: 'text/csv;charset=UTF-8' }); + const file = new File([content], 'profile.csv', { type: 'text/csv;charset=UTF-8' }); + const input = screen.getByTestId('file_input'); + + await waitFor(() => { + useEvent.upload(input, file); + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId('file_input')) + }) + + const metricInstances = within(screen.getByRole('table')); + const rows = metricInstances.getAllByRole('row'); + expect(rows).toHaveLength(4); + const row1 = within(rows[2]) + const row2 = within(rows[3]) + + expect(row1.getByText("org.opensciencegrid.htcondorce")).toBeInTheDocument() + expect(row1.getByText("ch.cern.HTCondorCE-JobState")).toBeInTheDocument() + expect(row2.getByText("org.opensciencegrid.htcondorce")).toBeInTheDocument() + expect(row2.getByText("ch.cern.HTCondorCE-JobSubmit")).toBeInTheDocument() + + expect(metricInstances.getAllByTestId(/remove-/i)).toHaveLength(2); + expect(metricInstances.getAllByTestId(/insert-/i)).toHaveLength(2); + + expect(screen.queryByText('Must be one of predefined metrics')).not.toBeInTheDocument() + + fireEvent.click(screen.getByRole('button', { name: /save/i })) + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /add/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockAddMetricProfile).toHaveBeenCalledWith({ + description: 'New central ARGO_MON profile.', + name: 'NEW_PROFILE', + services: [ + { + service: "org.opensciencegrid.htcondorce", + metrics: [ + "ch.cern.HTCondorCE-JobState", + "ch.cern.HTCondorCE-JobSubmit" + ] + } + ] + }) + }) + }) + + test("Test import csv with nonexisting metrics", async () => { + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole('button', { name: /csv/i })); + fireEvent.click(screen.getByRole('menuitem', { name: /import/i })); + + const csv = 'service,metric\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-CertValidity\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-JobSubmit\r\n'; + + const content = new Blob([csv], { type: 'text/csv;charset=UTF-8' }); + const file = new File([content], 'profile.csv', { type: 'text/csv;charset=UTF-8' }); + const input = screen.getByTestId('file_input'); + + await waitFor(() => { + useEvent.upload(input, file); + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId('file_input')) + }) + + const metricInstances = within(screen.getByRole('table')); + const rows = metricInstances.getAllByRole('row'); + expect(rows).toHaveLength(4); + const row1 = within(rows[2]) + const row2 = within(rows[3]) + + expect(row1.getByText("org.opensciencegrid.htcondorce")).toBeInTheDocument() + expect(row1.getByText("ch.cern.HTCondorCE-CertValidity")).toBeInTheDocument() + expect(row2.getByText("org.opensciencegrid.htcondorce")).toBeInTheDocument() + expect(row2.getByText("ch.cern.HTCondorCE-JobSubmit")).toBeInTheDocument() + + expect(metricInstances.getAllByTestId(/remove-/i)).toHaveLength(2); + expect(metricInstances.getAllByTestId(/insert-/i)).toHaveLength(2); + + expect(screen.queryByText('Must be one of predefined metrics')).toBeInTheDocument() + }) + + test("Test import csv, make some changes and save profile successfully", async () => { + mockAddMetricProfile.mockReturnValueOnce( + Promise.resolve({ + status: { + message: 'Metric profile Created', + code: '200' + }, + data: { + id: 'va0ahsh6-6rs0-14ho-xlh9-wahso4hie7iv', + links: { + self: 'string' + } + } + }) + ) + + renderAddView() + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument() + }) + + fireEvent.change(screen.getByTestId('name'), { target: { value: 'NEW_PROFILE' } }); + fireEvent.change(screen.getByLabelText(/description/i), { target: { value: 'New central ARGO_MON profile.' } }); + + await waitFor(() => { + selectEvent.select(screen.getAllByText("Select...")[0], 'ARGO') + }) + + fireEvent.click(screen.getByRole('button', { name: /csv/i })); + fireEvent.click(screen.getByRole('menuitem', { name: /import/i })); + + const csv = 'service,metric\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-JobState\r\norg.opensciencegrid.htcondorce,ch.cern.HTCondorCE-JobSubmit\r\n'; + + const content = new Blob([csv], { type: 'text/csv;charset=UTF-8' }); + const file = new File([content], 'profile.csv', { type: 'text/csv;charset=UTF-8' }); + const input = screen.getByTestId('file_input'); + + await waitFor(() => { + useEvent.upload(input, file); + }) + + await waitFor(() => { + expect(input.files[0]).toStrictEqual(file) + }) + expect(input.files.item(0)).toStrictEqual(file) + expect(input.files).toHaveLength(1) + + await waitFor(() => { + fireEvent.load(screen.getByTestId('file_input')) + }) + + await waitFor(() => { + expect(within(screen.getByRole("table")).getAllByRole("row")).toHaveLength(4) + }) + + fireEvent.click(screen.getByTestId("insert-0")) + + const metricInstances = within(screen.getByRole('table')); + var rows = metricInstances.getAllByRole('row'); + const row2 = within(rows[3]) + + await waitFor(() => { + selectEvent.select(row2.getAllByText("Select...")[0], "eu.argo.ams") + }) + + await waitFor(() => { + selectEvent.select(row2.getAllByText("Select...")[1], "argo.AMS-Check") + }) + + await waitFor(() => { + expect(screen.queryByText(/duplicated/i)).not.toBeInTheDocument() + }) + + expect(screen.queryByText('Must be one of predefined metrics')).not.toBeInTheDocument() + + fireEvent.click(screen.getByRole('button', { name: /save/i })) + await waitFor(() => { + expect(screen.getByRole('dialog', { title: /add/i })).toBeInTheDocument(); + }) + fireEvent.click(screen.getByRole('button', { name: /yes/i })); + + await waitFor(() => { + expect(mockAddMetricProfile).toHaveBeenCalledWith({ + description: 'New central ARGO_MON profile.', + name: 'NEW_PROFILE', + services: [ + { + service: "eu.argo.ams", + metrics: [ + "argo.AMS-Check" + ] + }, + { + service: "org.opensciencegrid.htcondorce", + metrics: [ + "ch.cern.HTCondorCE-JobState", + "ch.cern.HTCondorCE-JobSubmit" + ] + } + ] + }) + }) + }) + test("Test importing tuples from constituting tenants for combined tenant", async () => { renderAddView(true) From 3fd131427212e53cc05662c0fdcf184b5c39f2c4 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 11:10:22 +0200 Subject: [PATCH 12/22] Add public API view for default ports --- poem/Poem/api/internal_views/metrictemplates.py | 11 +++++++++++ poem/Poem/api/urls_internal.py | 1 + 2 files changed, 12 insertions(+) diff --git a/poem/Poem/api/internal_views/metrictemplates.py b/poem/Poem/api/internal_views/metrictemplates.py index 83c0f19cf..799f32924 100644 --- a/poem/Poem/api/internal_views/metrictemplates.py +++ b/poem/Poem/api/internal_views/metrictemplates.py @@ -1111,3 +1111,14 @@ def post(self, request): status_code=status.HTTP_401_UNAUTHORIZED, detail="You do not have permission to add ports" ) + + +class ListPublicDefaultPorts(ListDefaultPorts): + authentication_classes = () + permission_classes = () + + def _denied(self): + return Response(status=status.HTTP_403_FORBIDDEN) + + def post(self, request): + return self._denied() diff --git a/poem/Poem/api/urls_internal.py b/poem/Poem/api/urls_internal.py index bfa9c86ec..35e22f5a9 100644 --- a/poem/Poem/api/urls_internal.py +++ b/poem/Poem/api/urls_internal.py @@ -93,6 +93,7 @@ path("metricconfiguration/", views_internal.ListMetricConfiguration.as_view(), name="metricconfiguration"), path("metricconfiguration/", views_internal.ListMetricConfiguration.as_view(), name="metricconfiguration"), path("default_ports/", views_internal.ListDefaultPorts.as_view(), name="default_ports"), + path("public_default_ports/", views_internal.ListPublicDefaultPorts.as_view(), name="default_ports"), path("probecandidates/", views_internal.ListProbeCandidates.as_view(), name="probecandidates"), path("probecandidates/", views_internal.ListProbeCandidates.as_view(), name="probecandidates"), path("probecandidatestatuses/", views_internal.ListProbeCandidateStatuses.as_view(), name="probecandidatestatus") From f04388350a2f3926493fc0fee79f7c41f4d39831 Mon Sep 17 00:00:00 2001 From: Katarina Zailac Date: Tue, 18 Jun 2024 11:43:00 +0200 Subject: [PATCH 13/22] Create public default ports in the UI --- poem/Poem/frontend/react/App.js | 18 +++ poem/Poem/frontend/react/DefaultPorts.js | 113 +++++++++++------- .../react/__tests__/DefaultPorts.test.js | 113 ++++++++++++++++-- 3 files changed, 188 insertions(+), 56 deletions(-) diff --git a/poem/Poem/frontend/react/App.js b/poem/Poem/frontend/react/App.js index 61bf98331..65eff1f51 100644 --- a/poem/Poem/frontend/react/App.js +++ b/poem/Poem/frontend/react/App.js @@ -1274,6 +1274,15 @@ const App = () => { } /> + + + + } + /> + { } /> + + + + } + /> + } /> diff --git a/poem/Poem/frontend/react/DefaultPorts.js b/poem/Poem/frontend/react/DefaultPorts.js index f23eeb296..a57c0ef99 100644 --- a/poem/Poem/frontend/react/DefaultPorts.js +++ b/poem/Poem/frontend/react/DefaultPorts.js @@ -58,7 +58,7 @@ const validationSchema = Yup.object().shape({ }) -const PortsList = ({ data }) => { +const PortsList = ({ data, publicView }) => { const backend = new Backend() const queryClient = useQueryClient() @@ -154,14 +154,17 @@ const PortsList = ({ data }) => {

Default ports

- + { + !publicView && + + }
@@ -181,9 +184,19 @@ const PortsList = ({ data }) => {
#Port namePort valueActionPort namePort valuePort namePort valueAction
- - - + + +