diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..2c352119 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e093..09523c0e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/webui/Add_@types.patch b/src/main/webui/Add_@types.patch index 8e35ccf4..bda392de 100644 --- a/src/main/webui/Add_@types.patch +++ b/src/main/webui/Add_@types.patch @@ -1,4 +1,5 @@ -+++ proposalToolSchemas.ts 2024-06-24 16:25:18 +--- proposalToolSchemas.ts.orig 2024-07-16 09:51:55 ++++ proposalToolSchemas.ts 2024-07-18 11:53:22 @@ -7,6 +7,8 @@ * A block of resources that have been allocated */ @@ -88,16 +89,16 @@ }; export type FileUpload = Record; -@@ -684,6 +693,8 @@ - export type ObsType = "TargetObservation" | "CalibrationObservation"; - +@@ -687,6 +696,8 @@ + * An observation - occurs in a single non-overlapping time period + */ export type Observation = { + "@type"?: string; //ObsType (see above) + _id?: number; /** * any constraints on the observation */ -@@ -706,6 +717,9 @@ +@@ -709,6 +720,9 @@ * An organisation that can perform astronomical observations */ export type Observatory = { @@ -107,7 +108,7 @@ xmlId?: string; /** * The name of the organization -@@ -762,6 +776,7 @@ +@@ -765,6 +779,7 @@ * a collection of configs that can be chosen to observe with. */ export type ObservingMode = { @@ -115,7 +116,7 @@ name?: string; description?: string; configurations?: ObservingConfiguration[]; -@@ -772,6 +787,7 @@ +@@ -775,6 +790,7 @@ * a complete proposal */ export type ObservingProposal = { @@ -123,7 +124,7 @@ /** * the proposal title */ -@@ -829,6 +845,8 @@ +@@ -832,6 +848,8 @@ * An institution that is a collection of people */ export type Organization = { @@ -132,7 +133,7 @@ /** * The name of the organization */ -@@ -1053,6 +1071,8 @@ +@@ -1056,6 +1074,8 @@ * Defines collection of resources and proposals for a particular observing season */ export type ProposalCycle = { @@ -141,7 +142,7 @@ /** * a human readable description of the cycle */ -@@ -1121,6 +1141,7 @@ +@@ -1124,6 +1144,7 @@ * A review of a proposal */ export type ProposalReview = { @@ -149,7 +150,7 @@ /** * Description */ -@@ -1203,6 +1224,7 @@ +@@ -1206,6 +1227,7 @@ * A real value with a unit. */ export type RealQuantity = { @@ -157,7 +158,7 @@ /** * Must conform to definition of unit in VOUnit spec. */ -@@ -1234,6 +1256,8 @@ +@@ -1237,6 +1259,8 @@ * A resource that will be consumed by allocating an observation from a proposal */ export type Resource = { @@ -166,7 +167,7 @@ /** * The amount of the resource * -@@ -1264,6 +1288,7 @@ +@@ -1267,6 +1291,7 @@ * a type of resource */ export type ResourceType = { @@ -174,7 +175,7 @@ /** * the name of the resource type */ -@@ -1276,6 +1301,7 @@ +@@ -1279,6 +1304,7 @@ * assigned to review the proposal */ export type Reviewer = { @@ -182,7 +183,7 @@ /** * person connected with the proposal */ -@@ -1307,6 +1333,8 @@ +@@ -1310,6 +1336,8 @@ /** * Science oriented definition of a spectral window. */ @@ -191,7 +192,7 @@ spectralWindowSetup?: SpectralWindowSetup; expectedSpectralLine?: ExpectedSpectralLine[]; }; -@@ -1351,6 +1379,7 @@ +@@ -1354,6 +1382,7 @@ * A SpaceFrame is specified by its reference frame (orientation), and a reference position (origin). Currently only standard reference frames are allowed. An equinox MUST be provided for pre-ICRS reference frames. A planetary ephemeris MAY be provided if relevant. If needed, but not provided, it is assumed to be 'DE405'. */ export type SpaceFrame = { @@ -199,7 +200,7 @@ /** * RefLocation defines the origin of the spatial coordinate space. This location is represented either by a standard reference position (for which the absolute location in phase space is known by definition), or a specified point in another Spatial frame. This object is used as the origin of the SpaceFrame here, but also to specify the Spatial Reference Position (refPosition) associated with other domain Frames. For example, in the Time domain, the Spatial Reference Position indicates that the 'time' values are the time that the 'event' occured at that location, which might be different from the detector location. */ -@@ -1373,6 +1402,7 @@ +@@ -1376,6 +1405,7 @@ * Specialized coordinate system for the Spatial domain. This object SHOULD include an appropriate SpaceFrame. In Appendix B, we define two standard spatial coordinate space instances (Spherical and Cartesian), which may be referenced in serializations. If a CoordSpace is not provided, it is assumed to be represented by a Standard Spherical Coordinate Space. */ export type SpaceSys = { @@ -207,7 +208,7 @@ xmlId?: string; /** * Abstract head of coordinate spaces related to physical properties. -@@ -1456,7 +1486,7 @@ +@@ -1459,7 +1489,7 @@ /** * person connected with the proposal */ @@ -216,7 +217,7 @@ uid?: string; inKeycloakRealm?: boolean; }; -@@ -1465,6 +1495,7 @@ +@@ -1468,6 +1498,7 @@ * an instance of a proposal that has been submitted */ export type SubmittedProposal = { @@ -224,7 +225,7 @@ /** * the date that the proposal was submitted * -@@ -1527,17 +1558,20 @@ +@@ -1530,17 +1561,20 @@ * A target source */ export type Target = { @@ -245,7 +246,7 @@ xmlId?: string; name?: string; }; -@@ -1546,6 +1580,7 @@ +@@ -1549,6 +1583,7 @@ * an observation of the scientific target */ export type TargetObservation = { @@ -253,15 +254,15 @@ /** * any constraints on the observation */ -@@ -1573,6 +1608,7 @@ +@@ -1576,6 +1611,7 @@ */ performance?: PerformanceParameters; spectrum?: ScienceSpectralWindow[]; + _id?: number; xmlId?: string; }; - -@@ -1676,6 +1712,10 @@ + +@@ -1679,6 +1715,10 @@ * particular time range */ export type TimingWindow = { diff --git a/src/main/webui/src/ProposalEditorView/observationFields/observationFieldsTable.tsx b/src/main/webui/src/ProposalEditorView/observationFields/observationFieldsTable.tsx index 79a7da50..c811d69c 100644 --- a/src/main/webui/src/ProposalEditorView/observationFields/observationFieldsTable.tsx +++ b/src/main/webui/src/ProposalEditorView/observationFields/observationFieldsTable.tsx @@ -33,16 +33,15 @@ function ObservationFieldsRow(props: ObservationFieldRowProps): ReactElement { if (field.isError) { return ( - - Error: {getErrorMessage(field.error)} + Error: {getErrorMessage(field.error)} + ) } if (field.isLoading) { return ( - - Loading... + Loading... ) } diff --git a/src/main/webui/src/ProposalEditorView/observations/edit.group.tsx b/src/main/webui/src/ProposalEditorView/observations/edit.group.tsx index eeb03105..5dd909d8 100644 --- a/src/main/webui/src/ProposalEditorView/observations/edit.group.tsx +++ b/src/main/webui/src/ProposalEditorView/observations/edit.group.tsx @@ -4,14 +4,14 @@ import {ObservationProps} from "./observationPanel.tsx"; import { Fieldset, Grid, Text, Stack, Space } from '@mantine/core'; import { CalibrationObservation, - CalibrationTargetIntendedUse, Observation, TargetObservation, + CalibrationTargetIntendedUse, Observation, Target, TargetObservation, TimingWindow } from 'src/generated/proposalToolSchemas.ts'; import { useForm, UseFormReturnType } from '@mantine/form'; import { fetchObservationResourceAddNewConstraint, fetchObservationResourceAddNewObservation, fetchObservationResourceReplaceField, - fetchObservationResourceReplaceTarget, fetchObservationResourceReplaceTechnicalGoal, + fetchObservationResourceReplaceTargets, fetchObservationResourceReplaceTechnicalGoal, fetchObservationResourceReplaceTimingWindow } from 'src/generated/proposalToolComponents.ts'; import {FormSubmitButton} from 'src/commonButtons/save.tsx'; @@ -35,7 +35,7 @@ export interface ObservationFormValues { observationId: number | undefined, observationType: ObservationType, calibrationUse: CalibrationTargetIntendedUse | undefined, - targetDBId: number | undefined, + targetDBId: number[] | undefined, techGoalId: number | undefined, fieldId: string | undefined, //string for Select to show existing value in edit-form timingWindows: TimingWindowGui[], @@ -101,15 +101,15 @@ export default function ObservationEditGroup( observationId: props.observation?._id, //required for deletion of timing windows observationType: observationType, calibrationUse: calibrationUse, - targetDBId: props.observation?.target?._id, + targetDBId: props.observation?.target! as number[], techGoalId: props.observation?.technicalGoal?._id, fieldId: props.observation?.field?._id ? String(props.observation?.field?._id) : undefined, timingWindows: initialTimingWindows }, validate: { - targetDBId: (value: number | undefined | string ) => - (value === undefined ? 'Please select a target' : null), + // targetDBId: (value: number | undefined | string ) => + // (value === undefined ? 'Please select a target' : null), techGoalId: (value: number | undefined | string) => (value === undefined ? 'Please select a technical goal' : null), fieldId: (value: string | undefined) => @@ -138,11 +138,17 @@ export default function ObservationEditGroup( form.onSubmit((values) => { if (newObservation) { //Creating new observation - let baseObservation : Observation = { - target: { + let targetList: Target[] = []; + + form.values.targetDBId?.map((thisTarget) =>{ + targetList.push({ "@type": "proposal:CelestialTarget", - "_id": values.targetDBId - }, + "_id": thisTarget + }) + }) + + let baseObservation : Observation = { + target: targetList, technicalGoal: { "_id": values.techGoalId }, @@ -221,15 +227,21 @@ export default function ObservationEditGroup( }) if (form.isDirty('targetDBId')) { - fetchObservationResourceReplaceTarget({ + let body: Target[] = []; + + form.values.targetDBId?.map((thisTarget) =>{ + body.push({ + "@type": "proposal:CelestialTarget", + "_id": thisTarget + }) + }) + + fetchObservationResourceReplaceTargets({ pathParams: { proposalCode: Number(selectedProposalCode), observationId: props.observation?._id! }, - body: { - "@type": "proposal:CelestialTarget", - "_id": form.values.targetDBId - } + body: body }) .then(()=>queryClient.invalidateQueries()) .catch(console.error) diff --git a/src/main/webui/src/ProposalEditorView/observations/observationTable.tsx b/src/main/webui/src/ProposalEditorView/observations/observationTable.tsx index 3c78570f..5777c329 100644 --- a/src/main/webui/src/ProposalEditorView/observations/observationTable.tsx +++ b/src/main/webui/src/ProposalEditorView/observations/observationTable.tsx @@ -84,7 +84,7 @@ export default function ObservationRow(observationId: ObservationId): ReactEleme {(observation?.["@type"] === 'proposal:TargetObservation') ? 'Target' : 'Calibration'} - Observation of '{observation?.target?.sourceName}' + Observation of {observation?.target?.length} target(s) @@ -125,7 +125,7 @@ export default function ObservationRow(observationId: ObservationId): ReactEleme {(observation?.["@type"] === 'proposal:TargetObservation') ? 'Target' : 'Calibration'} - Observation of '{observation?.target?.sourceName}' + Observation of {observation?.target?.length} target(s) @@ -170,7 +170,7 @@ export default function ObservationRow(observationId: ObservationId): ReactEleme return ( - {observation?.target?.sourceName} + Observation of {observation?.target?.length} target(s) {observation?.["@type"]=== 'proposal:TargetObservation' ? diff --git a/src/main/webui/src/ProposalEditorView/observations/targetType.form.tsx b/src/main/webui/src/ProposalEditorView/observations/targetType.form.tsx index 3c696d85..b62ca58a 100644 --- a/src/main/webui/src/ProposalEditorView/observations/targetType.form.tsx +++ b/src/main/webui/src/ProposalEditorView/observations/targetType.form.tsx @@ -142,7 +142,7 @@ export default function TargetTypeForm ( {/* only present the message about selecting a target when not selected */} {form.values.targetDBId === undefined || - form.values.targetDBId === NO_ROW_SELECTED + form.values.targetDBId.length == 0 ?

Please select a target.

@@ -158,12 +158,24 @@ export default function TargetTypeForm ( showButtons={false} isLoading={false} boundTargets={[]} - selectedTarget={ + selectedTargets={[ form.values.targetDBId != undefined ? - form.values.targetDBId : - NO_ROW_SELECTED} - setSelectedTarget={(value: number) => { - form.setFieldValue('targetDBId', value); + form.values.targetDBId[0] : + 0]} + setSelectTarget={(value: number) => { + const index = form.values.targetDBId?.indexOf(value); + console.log("index: " + index + " length= " + form.values.targetDBId?.length); + if(index === undefined || index == -1) { + if(form.values.targetDBId == undefined) { + form.setFieldValue('targetDBId', [value]); + } else { + const newTargets = form.values.targetDBId; + newTargets.push(value); + form.setFieldValue('targetDBId', newTargets); + } + } else { + form.values.targetDBId?.splice(index, 1); + } }} /> diff --git a/src/main/webui/src/ProposalEditorView/proposal/Overview.tsx b/src/main/webui/src/ProposalEditorView/proposal/Overview.tsx index dd7e26db..d5d197ce 100644 --- a/src/main/webui/src/ProposalEditorView/proposal/Overview.tsx +++ b/src/main/webui/src/ProposalEditorView/proposal/Overview.tsx @@ -17,8 +17,8 @@ import { import { CalibrationObservation, CalibrationTargetIntendedUse, - Investigator, - RealQuantity, + Investigator, ObjectIdentifier, + RealQuantity, Target, } from 'src/generated/proposalToolSchemas.ts'; import { IconNorthStar } from '@tabler/icons-react'; import { ReactElement, useRef } from 'react'; @@ -179,7 +179,7 @@ function ObservationAccordionLabel( */ interface ObservationContentProps { proposalCode: number; - targetId: number; + targetIds: number[]; technicalGoalId: number; } @@ -190,17 +190,21 @@ interface ObservationContentProps { * @constructor */ function ObservationAccordionContent( - {proposalCode, targetId, technicalGoalId} : ObservationContentProps) : + {proposalCode, targetIds, technicalGoalId} : ObservationContentProps) : ReactElement { + + const listOfTargets = [] as ObjectIdentifier []; + + targetIds.map((targetId: any) => listOfTargets.push({dbid: targetId, code: proposalCode.toString()})) + return ( //TODO: consider a Grid instead of Group { + const observations = proposalsData?.observations?.map((observation, index) => { //observation.target and observation.technicalGoal are NOT objects // but numbers here, specifically their DB id - let targetObj = proposalsData?.targets?.find((target) => - target._id === observation.target)! + //not sure if this will be needed, it's still work in progress + let targetObjs = [] as Target[]; + + observation.target?.map((obsTarget) => { + let targetObj = proposalsData?.targets?.find((target) => + target._id === obsTarget)! + + targetObjs.push(targetObj); + + }); let technicalGoalObj = proposalsData?.technicalGoals?.find((techGoal) => techGoal._id === observation.technicalGoal)! @@ -489,7 +502,7 @@ function OverviewPanel(): ReactElement { diff --git a/src/main/webui/src/ProposalEditorView/targets/TargetTable.tsx b/src/main/webui/src/ProposalEditorView/targets/TargetTable.tsx index ba0f7c9a..97337c5f 100644 --- a/src/main/webui/src/ProposalEditorView/targets/TargetTable.tsx +++ b/src/main/webui/src/ProposalEditorView/targets/TargetTable.tsx @@ -12,7 +12,6 @@ import DeleteButton from "src/commonButtons/delete"; import { TargetProps, TargetTableProps } from './targetProps.tsx'; import { ERROR_YELLOW, - NO_ROW_SELECTED, TABLE_HIGH_LIGHT_COLOR } from 'src/constants.tsx'; import {notifyError} from "../../commonPanel/notifications.tsx"; @@ -132,15 +131,18 @@ function TargetTableRow(props: TargetProps): ReactElement { const RowSelector = (targetId: number | undefined): void => { console.debug(`row ${targetId} was selected`); // handle not having a selection method - if (!props.setSelectedTarget) { + if (!props.setSelectTarget) { return; } // handle selection. - if (props.selectedTarget === targetId) { - props.setSelectedTarget(NO_ROW_SELECTED); - } else { - props.setSelectedTarget!(targetId!); + //FIXME this is wrong + if(props.selectedTargets !== undefined && targetId !== undefined) { + if ( props.selectedTargets.includes(targetId) ) { + props.setSelectTarget(targetId); + } else { + props.setSelectTarget(targetId); + } } } @@ -200,7 +202,7 @@ function TargetTableRow(props: TargetProps): ReactElement { // generate the full row. return ( {RowSelector(data?._id);}} - bg={props.selectedTarget === data?._id ? + bg={data?._id !== undefined && props.selectedTargets?.includes(data?._id) ? TABLE_HIGH_LIGHT_COLOR : undefined}> @@ -235,7 +237,7 @@ export function TargetTable(props: TargetTableProps): ReactElement { const theme = useMantineTheme(); return ( {TargetTableHeader(props)} @@ -252,8 +254,8 @@ export function TargetTable(props: TargetTableProps): ReactElement { key={String(item.dbid!)} showButtons={props.showButtons} boundTargets={props.boundTargets} - selectedTarget={props.selectedTarget} - setSelectedTarget={props.setSelectedTarget} + selectedTargets={props.selectedTargets} + setSelectTarget={props.setSelectTarget} />) }) } diff --git a/src/main/webui/src/ProposalEditorView/targets/targetPanel.tsx b/src/main/webui/src/ProposalEditorView/targets/targetPanel.tsx index 5a1e1f73..55d2aa81 100644 --- a/src/main/webui/src/ProposalEditorView/targets/targetPanel.tsx +++ b/src/main/webui/src/ProposalEditorView/targets/targetPanel.tsx @@ -41,12 +41,13 @@ export function TargetPanel(): ReactElement { } // acquire all the bound targets ids in observations. - let boundTargets: (number | undefined)[] | undefined; - boundTargets = proposalsData?.observations?.map((observation) => { - // extract the id. it seems the technical Goal returned here IS a number - // not the TechnicalGoal object it advertises. - return observation.target as number; - }); + let boundTargets: (number)[] = [0]; + + proposalsData?.observations?.map((observation) => { + observation.target?.map((id) => { + boundTargets?.push(id as number); + }) + }) return ( @@ -60,7 +61,7 @@ export function TargetPanel(): ReactElement { selectedProposalCode={selectedProposalCode} boundTargets={boundTargets} showButtons={true} - selectedTarget={undefined}/> + selectedTargets={undefined}/> } diff --git a/src/main/webui/src/ProposalEditorView/targets/targetProps.tsx b/src/main/webui/src/ProposalEditorView/targets/targetProps.tsx index fd457635..8348fdca 100644 --- a/src/main/webui/src/ProposalEditorView/targets/targetProps.tsx +++ b/src/main/webui/src/ProposalEditorView/targets/targetProps.tsx @@ -9,7 +9,7 @@ import { * @param {boolean} showButtons boolean saying if this row can show its buttons. * @param {string} key forced upon us. * @param {number []} boundTargets the target ids bound to observations. - * @param {number | undefined} selectedTarget the row to be highlighted in + * @param {number | undefined}[] selectedTargets the row(s) to be highlighted in * selected mode. If undefined, the view is selected mode is turned off. * @param {(value: number) => void}} setSelectedTarget function, if defined for * what to do when selected. @@ -20,8 +20,8 @@ export type TargetProps = { showButtons: boolean, key: string, boundTargets: (number | undefined)[] | undefined, - selectedTarget: number | undefined, - setSelectedTarget?: (value: number) => void, + selectedTargets: (number)[] | undefined, + setSelectTarget?: (value: number) => void, } /** @@ -34,7 +34,7 @@ export type TargetProps = { * @param {boolean} showButtons boolean flagging if buttons should be visible. * @param {(number | undefined)[] | undefined} boundTargets array of targets * bound to observations. - * @param {number | undefined} selectedTarget the row to be highlighted in + * @param {number | undefined}[] selectedTargets the row(s) to be highlighted in * selected mode. If undefined, the view is selected mode is turned off. * @param {(value: number) => void}} setSelectedTarget function, if defined for * what to do when selected. @@ -45,6 +45,6 @@ export type TargetTableProps = { selectedProposalCode: string | undefined, showButtons: boolean, boundTargets: (number | undefined)[] | undefined, - selectedTarget: number | undefined, - setSelectedTarget?: (value: number) => void, + selectedTargets: (number)[] | undefined, + setSelectTarget?: (value: number) => void, } \ No newline at end of file diff --git a/src/main/webui/src/ProposalManagerView/allocations/allocatedBlocks.table.tsx b/src/main/webui/src/ProposalManagerView/allocations/allocatedBlocks.table.tsx index d7152199..3c3c472d 100644 --- a/src/main/webui/src/ProposalManagerView/allocations/allocatedBlocks.table.tsx +++ b/src/main/webui/src/ProposalManagerView/allocations/allocatedBlocks.table.tsx @@ -1,8 +1,14 @@ import {AllocatedBlock} from "../../generated/proposalToolSchemas.ts"; -import {Group, Stack, Table} from "@mantine/core"; +import {Group, Stack, Table, Text} from "@mantine/core"; import DeleteButton from "../../commonButtons/delete.tsx"; import AllocatedBlockModal from "./allocatedBlock.modal.tsx"; import {ReactElement} from "react"; +import {modals} from "@mantine/modals"; +import {fetchAllocatedBlockResourceRemoveAllocatedBlock} from "../../generated/proposalToolComponents.ts"; +import {useParams} from "react-router-dom"; +import {useQueryClient} from "@tanstack/react-query"; +import {notifyError, notifySuccess} from "../../commonPanel/notifications.tsx"; +import getErrorMessage from "../../errorHandling/getErrorMessage.tsx"; export type AllocatedBlocksTableProps = { proposalTitle: string, @@ -13,6 +19,48 @@ export type AllocatedBlocksTableProps = { export default function AllocatedBlocksTable(props: AllocatedBlocksTableProps): ReactElement { + const {selectedCycleCode} = useParams(); + const queryClient = useQueryClient(); + + type DeleteProps = { + proposalTitle: string, + resourceName: string, + allocatedId: number, + blockId: number + } + + + const confirmDelete = (props: DeleteProps) => { + modals.openConfirmModal({ + title: "Delete '" + props.resourceName + "' from '" + props.proposalTitle + "'?", + centered: true, + children:( + + This will remove the '{props.resourceName}' resource block from '{props.proposalTitle}'. + Are you sure? + + ), + labels: {confirm: "Delete", cancel: "No, don't remove " + props.resourceName}, + confirmProps: {color: "red"}, + onConfirm: () => handleDelete({...props}) + }) + } + + const handleDelete = (props: DeleteProps) => { + fetchAllocatedBlockResourceRemoveAllocatedBlock({ + pathParams: { + cycleCode: Number(selectedCycleCode), + allocatedId: props.allocatedId, + blockId: props.blockId + } + }) + .then(() => queryClient.invalidateQueries()) + .then(() => notifySuccess("Deletion Successful", + "Deleted " + props.resourceName + " from " + props.proposalTitle)) + .catch(error => notifyError("Failed to delete " + props.resourceName + + " from " + props.proposalTitle, getErrorMessage(error))) + } + return ( {props.allocatedBlocks.length > 0 && @@ -43,6 +91,14 @@ function AllocatedBlocksTable(props: AllocatedBlocksTableProps): ReactElement /> confirmDelete( + { + proposalTitle: props.proposalTitle, + resourceName: ab.resource?.type?.name!, + allocatedId: props.allocatedProposalId, + blockId: ab._id! + } + )} /> diff --git a/src/main/webui/src/generated/proposalToolComponents.ts b/src/main/webui/src/generated/proposalToolComponents.ts index dd3f246f..336659b9 100644 --- a/src/main/webui/src/generated/proposalToolComponents.ts +++ b/src/main/webui/src/generated/proposalToolComponents.ts @@ -9174,7 +9174,7 @@ export const useObservationResourceReplaceField = ( }); }; -export type ObservationResourceReplaceTargetPathParams = { +export type ObservationResourceReplaceTargetsPathParams = { /** * @format int64 */ @@ -9185,25 +9185,27 @@ export type ObservationResourceReplaceTargetPathParams = { proposalCode: number; }; -export type ObservationResourceReplaceTargetError = +export type ObservationResourceReplaceTargetsError = Fetcher.ErrorWrapper; -export type ObservationResourceReplaceTargetVariables = { - body?: Schemas.Target; - pathParams: ObservationResourceReplaceTargetPathParams; +export type ObservationResourceReplaceTargetsRequestBody = Schemas.Target[]; + +export type ObservationResourceReplaceTargetsVariables = { + body?: ObservationResourceReplaceTargetsRequestBody; + pathParams: ObservationResourceReplaceTargetsPathParams; } & ProposalToolContext["fetcherOptions"]; -export const fetchObservationResourceReplaceTarget = ( - variables: ObservationResourceReplaceTargetVariables, +export const fetchObservationResourceReplaceTargets = ( + variables: ObservationResourceReplaceTargetsVariables, signal?: AbortSignal ) => proposalToolFetch< undefined, - ObservationResourceReplaceTargetError, - Schemas.Target, + ObservationResourceReplaceTargetsError, + ObservationResourceReplaceTargetsRequestBody, {}, {}, - ObservationResourceReplaceTargetPathParams + ObservationResourceReplaceTargetsPathParams >({ url: "/pst/api/proposals/{proposalCode}/observations/{observationId}/target", method: "put", @@ -9211,12 +9213,12 @@ export const fetchObservationResourceReplaceTarget = ( signal, }); -export const useObservationResourceReplaceTarget = ( +export const useObservationResourceReplaceTargets = ( options?: Omit< reactQuery.UseMutationOptions< undefined, - ObservationResourceReplaceTargetError, - ObservationResourceReplaceTargetVariables + ObservationResourceReplaceTargetsError, + ObservationResourceReplaceTargetsVariables >, "mutationFn" > @@ -9224,11 +9226,11 @@ export const useObservationResourceReplaceTarget = ( const { fetcherOptions } = useProposalToolContext(); return reactQuery.useMutation< undefined, - ObservationResourceReplaceTargetError, - ObservationResourceReplaceTargetVariables + ObservationResourceReplaceTargetsError, + ObservationResourceReplaceTargetsVariables >({ - mutationFn: (variables: ObservationResourceReplaceTargetVariables) => - fetchObservationResourceReplaceTarget({ + mutationFn: (variables: ObservationResourceReplaceTargetsVariables) => + fetchObservationResourceReplaceTargets({ ...fetcherOptions, ...variables, }), diff --git a/src/main/webui/src/generated/proposalToolSchemas.ts b/src/main/webui/src/generated/proposalToolSchemas.ts index 5e035261..da1a5878 100644 --- a/src/main/webui/src/generated/proposalToolSchemas.ts +++ b/src/main/webui/src/generated/proposalToolSchemas.ts @@ -139,9 +139,9 @@ export type CalibrationObservation = { */ constraints?: ObservingConstraint[]; /** - * A target source + * A reference to - The actual target of the observation */ - target?: Target; + target?: Target[]; /** * Definition of an observing field pointing */ @@ -542,7 +542,7 @@ export type GenericSys = { /** * The handedness of a coordinate space. For most cases, this will be a fixed value in the specification of the coordinate space. We provide this element to allow this flexibility when needed. In this document, it is used in the Pixel domain. */ -export type Handedness = "LEFT" | "RIGHT"; +export type Handedness = "left" | "right"; /** * An instrument that can be attached to a telescope - e.g. CCD, Radio Receiver @@ -692,6 +692,9 @@ export type ObjectIdentifier = { export type ObsType = "TargetObservation" | "CalibrationObservation"; +/** + * An observation - occurs in a single non-overlapping time period + */ export type Observation = { "@type"?: string; //ObsType (see above) _id?: number; @@ -700,9 +703,9 @@ export type Observation = { */ constraints?: ObservingConstraint[]; /** - * A target source + * A reference to - The actual target of the observation */ - target?: Target; + target?: Target[]; /** * Definition of an observing field pointing */ @@ -1586,9 +1589,9 @@ export type TargetObservation = { */ constraints?: ObservingConstraint[]; /** - * A target source + * A reference to - The actual target of the observation */ - target?: Target; + target?: Target[]; /** * Definition of an observing field pointing */ @@ -1782,6 +1785,7 @@ export type UserProfileAttributeMetadata = { }; }; group?: string; + multivalued?: boolean; }; export type UserProfileMetadata = { @@ -1790,25 +1794,26 @@ export type UserProfileMetadata = { }; export type UserRepresentation = { - self?: string; id?: string; + username?: string; + firstName?: string; + lastName?: string; + email?: string; + emailVerified?: boolean; + attributes?: { + [key: string]: string[]; + }; + userProfileMetadata?: UserProfileMetadata; + self?: string; origin?: string; /** * @format int64 */ createdTimestamp?: number; - username?: string; enabled?: boolean; totp?: boolean; - emailVerified?: boolean; - firstName?: string; - lastName?: string; - email?: string; federationLink?: string; serviceAccountClientId?: string; - attributes?: { - [key: string]: string[]; - }; credentials?: CredentialRepresentation[]; /** * @uniqueItems true @@ -1839,7 +1844,6 @@ export type UserRepresentation = { access?: { [key: string]: boolean; }; - userProfileMetadata?: UserProfileMetadata; }; /**