@@ -40,7 +43,7 @@ export function GuiderTargets() {
);
}
- if (p1Targets.length > 0) {
+ if (p1Targets.length) {
const p1Selected = p1Targets.find((t) => t.pk === configuration?.selectedP1Target);
displayProbes.push(
@@ -51,7 +54,7 @@ export function GuiderTargets() {
);
}
- if (p2Targets.length > 0) {
+ if (p2Targets.length) {
const p2Selected = p2Targets.find((t) => t.pk === configuration?.selectedP2Target);
displayProbes.push(
@@ -83,12 +86,7 @@ export function GuiderTargets() {
displayProbes
)}
-
);
}
diff --git a/src/components/Panels/Telescope/Targets/Target.tsx b/src/components/Panels/Telescope/Targets/Target.tsx
index 979953c..0bfd6b2 100644
--- a/src/components/Panels/Telescope/Targets/Target.tsx
+++ b/src/components/Panels/Telescope/Targets/Target.tsx
@@ -11,7 +11,7 @@ export function Target({
targetIndex = undefined,
}: {
target: TargetType;
- updateSelectedTarget(target: number): void;
+ updateSelectedTarget(this: void, target: number): void | Promise
;
selectedTarget?: number | null;
targetIndex?: number | undefined;
}) {
@@ -38,7 +38,7 @@ export function Target({
switch (e.detail) {
case 1:
clickRef.current = setTimeout(() => {
- updateSelectedTarget(target.pk);
+ void updateSelectedTarget(target.pk);
}, 300);
break;
case 2:
diff --git a/src/components/Panels/Telescope/Targets/TargetList.tsx b/src/components/Panels/Telescope/Targets/TargetList.tsx
index a7fcfe2..437bc1b 100644
--- a/src/components/Panels/Telescope/Targets/TargetList.tsx
+++ b/src/components/Panels/Telescope/Targets/TargetList.tsx
@@ -26,22 +26,22 @@ export function TargetList({ targets, type }: { targets: TargetType[]; type?: Ty
break;
}
- function updateSelectedTarget(targetPk: number) {
+ async function updateSelectedTarget(targetPk: number) {
switch (type) {
case 'OIWFS':
- updateConfiguration({
+ await updateConfiguration({
variables: { pk: configuration!.pk, selectedOiTarget: targetPk },
});
break;
case 'PWFS1':
- updateConfiguration({
+ await updateConfiguration({
variables: { pk: configuration!.pk, selectedP1Target: targetPk },
});
break;
case 'PWFS2':
- updateConfiguration({
+ await updateConfiguration({
variables: { pk: configuration!.pk, selectedP2Target: targetPk },
});
break;
@@ -50,7 +50,7 @@ export function TargetList({ targets, type }: { targets: TargetType[]; type?: Ty
case 'SCIENCE':
case 'BLINDOFFSET':
case 'FIXED':
- updateConfiguration({
+ await updateConfiguration({
variables: { pk: configuration!.pk, selectedTarget: targetPk },
});
break;
@@ -70,7 +70,12 @@ export function TargetList({ targets, type }: { targets: TargetType[]; type?: Ty
if (displayTargets.length === 0) {
// Return an empty target as placeholder
displayTargets.push(
- ({})} selectedTarget={0} />,
+ undefined}
+ selectedTarget={0}
+ />,
);
}
return (
diff --git a/src/components/Panels/Telescope/Targets/TargetSwapButton.test.tsx b/src/components/Panels/Telescope/Targets/TargetSwapButton.test.tsx
new file mode 100644
index 0000000..c6f3d4d
--- /dev/null
+++ b/src/components/Panels/Telescope/Targets/TargetSwapButton.test.tsx
@@ -0,0 +1,235 @@
+import { MockedResponse } from '@apollo/client/testing';
+import { GET_CONFIGURATION } from '@gql/configs/Configuration';
+import { Target } from '@gql/configs/gen/graphql';
+import { GET_INSTRUMENT } from '@gql/configs/Instrument';
+import { GET_ROTATOR } from '@gql/configs/Rotator';
+import { renderWithContext } from '@gql/render';
+import { NAVIGATE_STATE, NAVIGATE_STATE_SUBSCRIPTION } from '@gql/server/NavigateState';
+import { RESTORE_TARGET_MUTATION, SWAP_TARGET_MUTATION } from '@gql/server/TargetSwap';
+import { ResultOf, VariablesOf } from '@graphql-typed-document-node/core';
+import { RenderResult } from 'vitest-browser-react';
+import { TargetSwapButton } from './TargetSwapButton';
+
+describe(TargetSwapButton.name, () => {
+ let sut: RenderResult;
+
+ describe('onSwappedTarget is false', () => {
+ beforeEach(async () => {
+ sut = renderWithContext(, {
+ mocks: [getRotatorMock, getInstrumentMock, getConfigurationMock, swapTargetMock, ...navigateStatesMock(false)],
+ });
+ // wait for state to load
+ await expect.element(sut.getByRole('button')).toBeInTheDocument();
+ });
+
+ it('should render', async () => {
+ await expect.element(sut.getByRole('button')).toHaveTextContent('Point to Guide Star');
+ });
+
+ it('should swap target when onSwappedTarget is false', async () => {
+ await sut.getByRole('button').click();
+
+ expect(sut.container.querySelector('.p-button-loading')).toBeNull();
+ expect(swapTargetMock.result).toHaveBeenCalledOnce();
+ });
+ });
+
+ describe('onSwappedTarget is true', () => {
+ beforeEach(async () => {
+ sut = renderWithContext(, {
+ mocks: [
+ getRotatorMock,
+ getInstrumentMock,
+ getConfigurationMock,
+ restoreTargetMock,
+ ...navigateStatesMock(true),
+ ],
+ });
+ // wait for state to load
+ await expect.element(sut.getByRole('button')).toBeInTheDocument();
+ });
+
+ it('should restore target', async () => {
+ await expect.element(sut.getByRole('button')).toHaveTextContent('Point to Base');
+
+ await sut.getByRole('button').click();
+
+ expect(restoreTargetMock.result).toHaveBeenCalledOnce();
+ });
+ });
+});
+
+const selectedTarget = {
+ pk: 3,
+ id: 't-19e',
+ name: 'TYC 4517-185-1',
+ ra: {
+ degrees: 56.69542085833334,
+ hms: '03:46:46.901006',
+ },
+ dec: {
+ degrees: 80.07267194527778,
+ dms: '+80:04:21.618990',
+ },
+ az: null,
+ el: null,
+ epoch: 'J2000.000',
+ type: 'SCIENCE',
+ createdAt: '2024-09-25T11:57:29.410Z',
+};
+
+const getRotatorMock: MockedResponse> = {
+ request: {
+ query: GET_ROTATOR,
+ variables: {},
+ },
+ result: {
+ data: {
+ rotator: {
+ pk: 2,
+ angle: 0,
+ tracking: 'TRACKING',
+ },
+ },
+ },
+};
+
+const getInstrumentMock: MockedResponse> = {
+ request: {
+ query: GET_INSTRUMENT,
+ },
+ maxUsageCount: 5,
+ variableMatcher: () => true,
+ result: {
+ data: {
+ instrument: {
+ pk: 1,
+ wfs: 'OIWFS',
+ iaa: 359.877,
+ issPort: 3,
+ focusOffset: 0.0,
+ name: 'GMOS_SOUTH',
+ ao: false,
+ originX: 0.0,
+ originY: 0.0,
+ extraParams: {},
+ },
+ },
+ },
+};
+
+const navigateStatesMock = (onSwappedTarget: boolean) => [
+ {
+ request: {
+ query: NAVIGATE_STATE,
+ variables: {},
+ },
+ result: {
+ data: {
+ navigateState: {
+ onSwappedTarget,
+ },
+ },
+ },
+ } satisfies MockedResponse>,
+ {
+ request: {
+ query: NAVIGATE_STATE_SUBSCRIPTION,
+ variables: {},
+ },
+ result: {
+ data: {
+ navigateState: {
+ onSwappedTarget,
+ },
+ },
+ },
+ } satisfies MockedResponse>,
+];
+
+const swapTargetMock: MockedResponse<
+ ResultOf,
+ VariablesOf
+> = {
+ request: {
+ query: SWAP_TARGET_MUTATION,
+ variables: {
+ swapConfig: {
+ acParams: {
+ iaa: { degrees: 359.877 },
+ focusOffset: { micrometers: 0 },
+ agName: 'GMOS_SOUTH',
+ origin: { x: { micrometers: 0 }, y: { micrometers: 0 } },
+ },
+ rotator: { ipa: { degrees: 0 }, mode: 'TRACKING' },
+ guideTarget: {
+ id: selectedTarget.id,
+ name: selectedTarget.name,
+ sidereal: {
+ ra: { hms: selectedTarget.ra.hms },
+ dec: { dms: selectedTarget.dec.dms },
+ epoch: selectedTarget.epoch,
+ },
+ },
+ },
+ },
+ },
+ result: vi.fn().mockImplementation(() => ({ data: { swapTarget: { result: 'SUCCESS', msg: '' } } })),
+};
+
+const restoreTargetMock: MockedResponse<
+ ResultOf,
+ VariablesOf
+> = {
+ request: {
+ query: RESTORE_TARGET_MUTATION,
+ variables: {
+ config: {
+ instrument: 'GMOS_SOUTH',
+ instParams: {
+ iaa: { degrees: 359.877 },
+ focusOffset: { micrometers: 0 },
+ agName: 'GMOS_SOUTH',
+ origin: { x: { micrometers: 0 }, y: { micrometers: 0 } },
+ },
+ rotator: { ipa: { degrees: 0 }, mode: 'TRACKING' },
+ sourceATarget: {
+ id: selectedTarget.id,
+ name: selectedTarget.name,
+ sidereal: {
+ ra: { hms: selectedTarget.ra.hms },
+ dec: { dms: selectedTarget.dec.dms },
+ epoch: selectedTarget.epoch,
+ },
+ },
+ },
+ },
+ },
+ result: vi.fn().mockImplementation(() => ({ data: { restoreTarget: { result: 'SUCCESS', msg: '' } } })),
+};
+
+const getConfigurationMock: MockedResponse> = {
+ request: {
+ query: GET_CONFIGURATION,
+ },
+ variableMatcher: () => true,
+ result: {
+ data: {
+ configuration: {
+ pk: 1,
+ site: 'GN',
+ selectedTarget: 3,
+ selectedOiTarget: 8,
+ selectedP1Target: null,
+ selectedP2Target: null,
+ oiGuidingType: 'NORMAL',
+ p1GuidingType: 'NORMAL',
+ p2GuidingType: 'NORMAL',
+ obsTitle: 'Markarian 573',
+ obsId: 'o-1e1',
+ obsInstrument: 'GMOS_NORTH',
+ obsSubtitle: null,
+ },
+ },
+ },
+};
diff --git a/src/components/Panels/Telescope/Targets/TargetSwapButton.tsx b/src/components/Panels/Telescope/Targets/TargetSwapButton.tsx
new file mode 100644
index 0000000..7ad5031
--- /dev/null
+++ b/src/components/Panels/Telescope/Targets/TargetSwapButton.tsx
@@ -0,0 +1,106 @@
+import { useCanEdit } from '@/components/atoms/auth';
+import { useToast } from '@/Helpers/toast';
+import { useConfiguration } from '@gql/configs/Configuration';
+import { Target } from '@gql/configs/gen/graphql';
+import { useInstrument } from '@gql/configs/Instrument';
+import { useRotator } from '@gql/configs/Rotator';
+import {
+ Instrument as InstrumentName,
+ InstrumentSpecificsInput,
+ RotatorTrackingInput,
+ TargetPropertiesInput,
+} from '@gql/server/gen/graphql';
+import { useNavigateState } from '@gql/server/NavigateState';
+import { useRestoreTarget, useSwapTarget } from '@gql/server/TargetSwap';
+import { Button } from 'primereact/button';
+
+export function TargetSwapButton({ selectedTarget }: { selectedTarget: Target | undefined }) {
+ const canEdit = useCanEdit();
+ const toast = useToast();
+
+ const { data, loading: stateLoading } = useNavigateState();
+ const [swapTarget, { loading: swapLoading }] = useSwapTarget();
+ const [restoreTarget, { loading: restoreLoading }] = useRestoreTarget();
+
+ const { data: configurationData, loading: configurationLoading } = useConfiguration();
+ const configuration = configurationData?.configuration;
+
+ const { data: instrumentData, loading: instrumentLoading } = useInstrument({
+ variables: { name: configuration?.obsInstrument ?? '', issPort: 3, wfs: 'NONE' },
+ });
+ const instrument = instrumentData?.instrument;
+ const { data: rotatorData, loading: rotatorLoading } = useRotator();
+ const rotator = rotatorData?.rotator;
+
+ const loading =
+ stateLoading || swapLoading || restoreLoading || instrumentLoading || rotatorLoading || configurationLoading;
+
+ const disabled = !canEdit;
+
+ const label = data?.onSwappedTarget ? 'Point to Base' : 'Point to Guide Star';
+
+ const onClick = () => {
+ if (selectedTarget?.id && instrument && rotator) {
+ // TODO: other inputs for swap/nonswap
+ const instrumentInput: InstrumentSpecificsInput = {
+ iaa: { degrees: instrument.iaa },
+ focusOffset: { micrometers: instrument.focusOffset },
+ agName: instrument.name,
+ origin: { x: { micrometers: instrument.originX }, y: { micrometers: instrument.originY } },
+ };
+ const rotatorInput: RotatorTrackingInput = { ipa: { degrees: rotator.angle }, mode: rotator.tracking };
+ const targetInput: TargetPropertiesInput = {
+ id: selectedTarget.id,
+ name: selectedTarget.name,
+ sidereal: {
+ ra: { hms: selectedTarget?.ra?.hms },
+ dec: { dms: selectedTarget?.dec?.dms },
+ epoch: selectedTarget?.epoch,
+ },
+ // nonsidereal: // <- ???
+ // wavelength: {nanometers: } // <- ???
+ };
+
+ if (data?.onSwappedTarget) {
+ void restoreTarget({
+ variables: {
+ config: {
+ instrument: instrument.name as InstrumentName,
+ instParams: instrumentInput,
+ rotator: rotatorInput,
+ sourceATarget: targetInput,
+ },
+ },
+ });
+ } else {
+ void swapTarget({
+ variables: {
+ swapConfig: {
+ acParams: instrumentInput,
+ rotator: rotatorInput,
+ guideTarget: targetInput,
+ },
+ },
+ });
+ }
+ } else {
+ let detail;
+ if (!selectedTarget) {
+ detail = 'No target';
+ } else if (!instrument) {
+ detail = 'No instrument';
+ } else if (!rotator) {
+ detail = 'No rotator';
+ } else {
+ detail = 'Unknown error';
+ }
+ toast?.show({
+ severity: 'warn',
+ summary: data?.onSwappedTarget ? 'Cannot restore target' : 'Cannot swap target',
+ detail,
+ });
+ }
+ };
+
+ return ;
+}
diff --git a/src/components/Panels/Telescope/Targets/UpdateGuideTargets.tsx b/src/components/Panels/Telescope/Targets/UpdateGuideTargets.tsx
index 8910118..ce01eaf 100644
--- a/src/components/Panels/Telescope/Targets/UpdateGuideTargets.tsx
+++ b/src/components/Panels/Telescope/Targets/UpdateGuideTargets.tsx
@@ -1,23 +1,22 @@
-import { useGetGuideTargets } from '@gql/odb/Observation';
-import { TargetInput } from '@/types';
-import { useRef } from 'react';
-import { Button } from 'primereact/button';
-import { Toast } from 'primereact/toast';
-import { useRemoveAndCreateWfsTargets } from '@gql/configs/Target';
import { useSetLoadingGuideTarget } from '@/components/atoms/guideTarget';
+import { useToast } from '@/Helpers/toast';
+import { TargetInput } from '@/types';
import { useConfiguration } from '@gql/configs/Configuration';
+import { useRemoveAndCreateWfsTargets } from '@gql/configs/Target';
+import { useGetGuideTargets } from '@gql/odb/Observation';
+import { Button } from 'primereact/button';
export function UpdateGuideTargets({ canEdit }: { canEdit: boolean }) {
const configuration = useConfiguration().data?.configuration;
const setLoadingGuideTarget = useSetLoadingGuideTarget();
const [getGuideTargets] = useGetGuideTargets();
const removeAndCreateWfsTargets = useRemoveAndCreateWfsTargets();
- const toast = useRef(null);
+ const toast = useToast();
function calculateGuideTargets() {
setLoadingGuideTarget(true);
const crtTime = new Date().toISOString();
- getGuideTargets({
+ void getGuideTargets({
variables: {
observationId: configuration!.obsId!,
observationTime: crtTime,
@@ -71,11 +70,10 @@ export function UpdateGuideTargets({ canEdit }: { canEdit: boolean }) {
},
async onError(err) {
setLoadingGuideTarget(false);
- toast.current?.show({
+ toast?.show({
severity: 'error',
- summary: 'Error',
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
- detail: err.toString(),
+ summary: err.name,
+ detail: err.message,
life: 5000,
});
console.log(err);
@@ -105,18 +103,15 @@ export function UpdateGuideTargets({ canEdit }: { canEdit: boolean }) {
}
return (
- <>
-
-
- >
+
);
}
diff --git a/src/components/Panels/WavefrontSensors/AcquisitionCamera/MainControls.tsx b/src/components/Panels/WavefrontSensors/AcquisitionCamera/MainControls.tsx
index 5426a9b..4afb674 100644
--- a/src/components/Panels/WavefrontSensors/AcquisitionCamera/MainControls.tsx
+++ b/src/components/Panels/WavefrontSensors/AcquisitionCamera/MainControls.tsx
@@ -4,11 +4,11 @@ import { Dropdown } from 'primereact/dropdown';
import { InputNumber } from 'primereact/inputnumber';
export default function MainControls({ canEdit }: { canEdit: boolean }) {
- const [slider, setSlider] = useState(10);
- const upKey = useRef(null);
- const downKey = useRef(null);
- const leftKey = useRef(null);
- const rightKey = useRef(null);
+ const [slider, setSlider] = useState(10);
+ const upKey = useRef(null);
+ const downKey = useRef(null);
+ const leftKey = useRef(null);
+ const rightKey = useRef(null);
// useEffect(() => {
// document.addEventListener("keydown", keyPressed)
diff --git a/src/components/Panels/WavefrontSensors/WavefrontSensor/WavefrontSensor.tsx b/src/components/Panels/WavefrontSensors/WavefrontSensor/WavefrontSensor.tsx
index 828cbe4..b15f6c3 100644
--- a/src/components/Panels/WavefrontSensors/WavefrontSensor/WavefrontSensor.tsx
+++ b/src/components/Panels/WavefrontSensors/WavefrontSensor/WavefrontSensor.tsx
@@ -98,7 +98,7 @@ export default function WavefrontSensor({
{ label: '100', value: 100.0 },
{ label: '200', value: 200.0 },
]}
- onChange={(e) => setFreq(e.value)}
+ onChange={(e) => setFreq(e.value as number)}
/>
{observeButton}
Save
diff --git a/src/components/Shared/Title/Title.test.tsx b/src/components/Shared/Title/Title.test.tsx
index 1c22642..65d47da 100644
--- a/src/components/Shared/Title/Title.test.tsx
+++ b/src/components/Shared/Title/Title.test.tsx
@@ -1,6 +1,8 @@
-import { beforeEach, describe, expect } from 'vitest';
-import { render, fireEvent, screen } from '@testing-library/react';
+import { beforeEach, describe, expect, Mock } from 'vitest';
import { Title } from './Title';
+import { render } from 'vitest-browser-react';
+import { page } from '@vitest/browser/context';
+import { userEvent } from '@vitest/browser/context';
describe('Title test with args', () => {
const title = 'My title';
@@ -15,78 +17,85 @@ describe('Title test with args', () => {
const nextPanelFunction = vi.spyOn(nextPanelObject, 'nextPanel');
const prevPanelFunction = vi.spyOn(prevPanelObject, 'prevPanel');
- let container: HTMLElement = null!;
- beforeEach(() => {
- ({ container } = render(
+ beforeEach(async () => {
+ render(
Hola
,
- ));
+ );
+ await expect.element(page.getByText(title)).toBeInTheDocument();
});
- it('should show title', () => {
- expect(screen.getByText(title)).toBeDefined();
+ it('should show title', async () => {
+ await expect.element(page.getByText(title)).toBeInTheDocument();
});
- it('should have prev and next panel buttons', () => {
- expect(container.getElementsByClassName('p-panel').length).toBe(1);
- expect(container.getElementsByClassName('n-panel').length).toBe(1);
+ it('should have prev and next panel buttons', async () => {
+ await expect.element(page.getByText('Previous panel')).toBeInTheDocument();
+ await expect.element(page.getByText('Next panel')).toBeInTheDocument();
});
- it('should show children', () => {
- expect(screen.getByText('Hola')).toBeDefined();
+ it('should show children', async () => {
+ await expect.element(page.getByText('Hola')).toBeInTheDocument();
});
- it('functions should be called', () => {
- fireEvent(container.getElementsByClassName('n-panel')[0], new MouseEvent('click', { bubbles: true }));
+ it('functions should be called', async () => {
+ await userEvent.click(page.getByText('Next panel'));
+ await userEvent.click(page.getByText('Previous panel'));
- fireEvent(container.getElementsByClassName('p-panel')[0], new MouseEvent('click', { bubbles: true }));
- expect(nextPanelFunction).toHaveBeenCalledTimes(1);
- expect(prevPanelFunction).toHaveBeenCalledTimes(1);
+ expect(nextPanelFunction).toHaveBeenCalledOnce();
+ expect(prevPanelFunction).toHaveBeenCalledOnce();
});
});
describe('Title test with args', () => {
const title = 'My title';
- const nextPanel = () => undefined;
- const prevPanel = () => undefined;
+ let nextPanelMock: Mock;
+ let prevPanelMock: Mock;
- const nextPanelMock = vi.fn().mockImplementation(nextPanel);
- const prevPanelMock = vi.fn().mockImplementation(prevPanel);
+ beforeEach(() => {
+ nextPanelMock = vi.fn();
+ prevPanelMock = vi.fn();
+ });
- it('functions should be called', () => {
- const { container } = render(
+ it('functions should be called', async () => {
+ render(
Hola
,
);
- fireEvent(container.getElementsByClassName('n-panel')[0], new MouseEvent('click', { bubbles: true }));
+ await expect.element(page.getByText(title)).toBeInTheDocument();
+ await userEvent.click(page.getByText('Next panel'));
- fireEvent(container.getElementsByClassName('p-panel')[0], new MouseEvent('click', { bubbles: true }));
- expect(nextPanelMock).toHaveBeenCalledTimes(1);
- expect(prevPanelMock).toHaveBeenCalledTimes(1);
+ await userEvent.click(page.getByText('Previous panel'));
+
+ expect(nextPanelMock).toHaveBeenCalledOnce();
+ expect(prevPanelMock).toHaveBeenCalledOnce();
});
});
describe('Title test without args', () => {
const title = 'My title';
- it('should show title', () => {
+ it('should show title', async () => {
render();
- expect(screen.getByText(title)).toBeDefined();
+ await expect.element(page.getByText(title)).toBeInTheDocument();
});
- it('should not have prev and next panel button if no functions are defined', () => {
+ it('should not have prev and next panel button if no functions are defined', async () => {
const { container } = render();
- expect(container.getElementsByClassName('p-panel').length).toBe(0);
- expect(container.getElementsByClassName('n-panel').length).toBe(0);
+ await expect.element(page.getByText(title)).toBeInTheDocument();
+
+ expect(container.getElementsByClassName('p-panel')).toHaveLength(0);
+ expect(container.getElementsByClassName('n-panel')).toHaveLength(0);
});
- it('should not show children neither buttons if not defined', () => {
+ it('should not show children neither buttons if not defined', async () => {
const { container } = render();
- expect(container.firstChild?.childNodes.length).toBe(1);
+ await expect.element(page.getByText(title)).toBeInTheDocument();
+ expect(container.firstChild?.childNodes).toHaveLength(1);
});
});
@@ -99,8 +108,10 @@ describe('Title with children', () => {
children.push(node {i});
}
- it('should render title plus children created', () => {
+ it('should render title plus children created', async () => {
const instance = render({children});
- expect(instance.container.firstChild?.childNodes.length).toBe(CHILDREN + 1);
+ await expect.element(page.getByText(title)).toBeInTheDocument();
+
+ expect(instance.container.firstChild?.childNodes).toHaveLength(CHILDREN + 1);
});
});
diff --git a/src/components/Shared/Title/Title.tsx b/src/components/Shared/Title/Title.tsx
index 10265fe..b7e2c36 100644
--- a/src/components/Shared/Title/Title.tsx
+++ b/src/components/Shared/Title/Title.tsx
@@ -1,10 +1,10 @@
import { OverlayPanel } from 'primereact/overlaypanel';
-import { ReactNode, useRef } from 'react';
+import { MouseEventHandler, ReactNode, useRef } from 'react';
interface ParamsInterface {
title: string;
- prevPanel?: any;
- nextPanel?: any;
+ prevPanel?: MouseEventHandler;
+ nextPanel?: MouseEventHandler;
children?: ReactNode;
className?: string;
}
@@ -13,18 +13,20 @@ export function Title({ title, prevPanel, nextPanel, children, className = '' }:
let prevPanelDisplay = null;
if (prevPanel) {
prevPanelDisplay = (
-
+
+ Previous panel
+
);
}
let nextPanelDisplay = null;
if (nextPanel) {
nextPanelDisplay = (
-
+
+ Next panel
+
);
}
return (
diff --git a/src/gql/ApolloConfigs.ts b/src/gql/ApolloConfigs.ts
index afb2706..561d46a 100644
--- a/src/gql/ApolloConfigs.ts
+++ b/src/gql/ApolloConfigs.ts
@@ -1,5 +1,6 @@
// Apollo
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink, defaultDataIdFromObject } from '@apollo/client';
+import { onError } from '@apollo/client/link/error';
// Subscription channel
import { WebSocketLink } from '@apollo/client/link/ws';
@@ -18,6 +19,19 @@ const withAbsoluteUri = (uri: string, isWs = false) => {
return isWs ? newUri.replace(/^http/, 'ws') : newUri;
};
+// Log errors to the console
+const errorLink = onError(({ graphQLErrors, networkError }) => {
+ if (graphQLErrors)
+ graphQLErrors.forEach(({ message, locations, path }) =>
+ console.warn(
+ `[GraphQL error]: ${message.trim()}
+ Path: ${(path ?? [])?.join(', ')} ${(locations ?? [])?.map((l) => `[${l.line}:${l.column}]`).join(', ')}`,
+ ),
+ );
+
+ if (networkError) console.error(`[Network error]`, networkError);
+});
+
export function createClient(env: Environment) {
const navigateCommandServer = new HttpLink({ uri: withAbsoluteUri(env.navigateServerURI) });
@@ -29,24 +43,27 @@ export function createClient(env: Environment) {
return new ApolloClient({
name: 'navigate-ui',
- link: ApolloLink.split(
- (operation) => operation.getContext().clientName === 'odb',
- odbLink,
+ link: ApolloLink.from([
+ errorLink,
ApolloLink.split(
- (operation) => operation.getContext().clientName === 'navigateConfigs',
- navigateConfigs,
+ (operation) => operation.getContext().clientName === 'odb',
+ odbLink,
ApolloLink.split(
- ({ query }) => {
- const definition = getMainDefinition(query);
- return (
- definition.kind === Kind.OPERATION_DEFINITION && definition.operation === OperationTypeNode.SUBSCRIPTION
- );
- },
- wsLink,
- navigateCommandServer,
+ (operation) => operation.getContext().clientName === 'navigateConfigs',
+ navigateConfigs,
+ ApolloLink.split(
+ ({ query }) => {
+ const definition = getMainDefinition(query);
+ return (
+ definition.kind === Kind.OPERATION_DEFINITION && definition.operation === OperationTypeNode.SUBSCRIPTION
+ );
+ },
+ wsLink,
+ navigateCommandServer,
+ ),
),
),
- ),
+ ]),
cache: new InMemoryCache({
dataIdFromObject(responseObject) {
// Configure primary-key fields for cache normalization to use 'pk' field
diff --git a/src/gql/configs/Configuration.ts b/src/gql/configs/Configuration.ts
index 099576a..3344939 100644
--- a/src/gql/configs/Configuration.ts
+++ b/src/gql/configs/Configuration.ts
@@ -1,7 +1,7 @@
import { useMutation, useQuery } from '@apollo/client';
import { graphql } from './gen';
-const GET_CONFIGURATION = graphql(`
+export const GET_CONFIGURATION = graphql(`
query getConfiguration {
configuration {
pk
diff --git a/src/gql/configs/Instrument.ts b/src/gql/configs/Instrument.ts
index 1bb4f0a..b68d6ec 100644
--- a/src/gql/configs/Instrument.ts
+++ b/src/gql/configs/Instrument.ts
@@ -34,7 +34,7 @@ export function useGetDistinctPorts() {
return queryFunction;
}
-const GET_INSTRUMENTS = graphql(`
+export const GET_INSTRUMENTS = graphql(`
query getInstruments($name: String!, $issPort: Int!) {
instruments(name: $name, issPort: $issPort) {
pk
@@ -59,8 +59,8 @@ export function useGetInstruments() {
return queryFunction;
}
-const GET_INSTRUMENT = graphql(`
- query getInstrument($name: String!, $issPort: Int!, $wfs: WfsType) {
+export const GET_INSTRUMENT = graphql(`
+ query getInstrument($name: String, $issPort: Int, $wfs: WfsType) {
instrument(name: $name, issPort: $issPort, wfs: $wfs) {
pk
name
diff --git a/src/gql/configs/Rotator.ts b/src/gql/configs/Rotator.ts
index 928096a..0ed8c07 100644
--- a/src/gql/configs/Rotator.ts
+++ b/src/gql/configs/Rotator.ts
@@ -1,7 +1,7 @@
import { useMutation, useQuery } from '@apollo/client';
import { graphql } from './gen';
-const GET_ROTATOR = graphql(`
+export const GET_ROTATOR = graphql(`
query getRotator {
rotator {
pk
diff --git a/src/gql/configs/Target.ts b/src/gql/configs/Target.ts
index 89e4170..63e9b5b 100644
--- a/src/gql/configs/Target.ts
+++ b/src/gql/configs/Target.ts
@@ -2,6 +2,7 @@ import { useMutation, useQuery } from '@apollo/client';
import { graphql } from './gen';
import { isBaseTarget, isOiTarget, isP1Target, isP2Target } from '@gql/util';
import { useMemo } from 'react';
+import { Target } from './gen/graphql';
const GET_TARGETS = graphql(`
query getTargets {
@@ -38,12 +39,13 @@ export function useTargets() {
});
const filteredData = useMemo(() => {
- const targets = result.data?.targets ?? [];
+ const targets: Target[] = result.data?.targets ?? [];
return {
baseTargets: targets.filter(isBaseTarget),
oiTargets: targets.filter(isOiTarget),
p1Targets: targets.filter(isP1Target),
p2Targets: targets.filter(isP2Target),
+ allTargets: targets,
};
}, [result.data]);
diff --git a/src/gql/configs/gen/gql.ts b/src/gql/configs/gen/gql.ts
index c3e6fa4..8b912cb 100644
--- a/src/gql/configs/gen/gql.ts
+++ b/src/gql/configs/gen/gql.ts
@@ -47,7 +47,7 @@ const documents = {
types.GetDistinctPortsDocument,
'\n query getInstruments($name: String!, $issPort: Int!) {\n instruments(name: $name, issPort: $issPort) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n':
types.GetInstrumentsDocument,
- '\n query getInstrument($name: String!, $issPort: Int!, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n':
+ '\n query getInstrument($name: String, $issPort: Int, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n':
types.GetInstrumentDocument,
'\n query getMechanism {\n mechanism {\n pk\n mcs\n mcsPark\n mcsUnwrap\n scs\n crcs\n crcsPark\n crcsUnwrap\n pwfs1\n pwfs1Park\n pwfs1Unwrap\n pwfs2\n pwfs2Park\n pwfs2Unwrap\n oiwfs\n oiwfsPark\n odgw\n odgwPark\n aowfs\n aowfsPark\n dome\n domePark\n domeMode\n shutters\n shuttersPark\n shutterMode\n shutterAperture\n wVGate\n wVGateClose\n wVGateValue\n eVGate\n eVGateClose\n eVGateValue\n agScienceFoldPark\n agAoFoldPark\n agAcPickoffPark\n agParkAll\n }\n }\n':
types.GetMechanismDocument,
@@ -193,8 +193,8 @@ export function graphql(
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
- source: '\n query getInstrument($name: String!, $issPort: Int!, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n',
-): (typeof documents)['\n query getInstrument($name: String!, $issPort: Int!, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n'];
+ source: '\n query getInstrument($name: String, $issPort: Int, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n',
+): (typeof documents)['\n query getInstrument($name: String, $issPort: Int, $wfs: WfsType) {\n instrument(name: $name, issPort: $issPort, wfs: $wfs) {\n pk\n name\n iaa\n issPort\n focusOffset\n wfs\n originX\n originY\n ao\n extraParams\n }\n }\n'];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
diff --git a/src/gql/configs/gen/graphql.ts b/src/gql/configs/gen/graphql.ts
index 32bd88d..56bb3fa 100644
--- a/src/gql/configs/gen/graphql.ts
+++ b/src/gql/configs/gen/graphql.ts
@@ -907,8 +907,8 @@ export type GetInstrumentsQuery = {
};
export type GetInstrumentQueryVariables = Exact<{
- name: Scalars['String']['input'];
- issPort: Scalars['Int']['input'];
+ name?: InputMaybe;
+ issPort?: InputMaybe;
wfs?: InputMaybe;
}>;
@@ -2510,12 +2510,12 @@ export const GetInstrumentDocument = {
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
- type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } } },
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
},
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'issPort' } },
- type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'Int' } } },
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'Int' } },
},
{
kind: 'VariableDefinition',
diff --git a/src/gql/odb/gen/graphql.ts b/src/gql/odb/gen/graphql.ts
index 0225a01..69e5325 100644
--- a/src/gql/odb/gen/graphql.ts
+++ b/src/gql/odb/gen/graphql.ts
@@ -6158,10 +6158,22 @@ export type TargetEnvironment = {
* range. In this case, the `end` of one period will be the same as the `start` of the next period.
*/
guideAvailability: Array;
- /** The guide star(s) and related information */
- guideEnvironment?: Maybe;
+ /**
+ * The guide target(s) and related information.
+ * If a guide target has been set via `guideTargetName`, that target will be
+ * returned. If it not found or not usable, an error will be returned.
+ * If no guide target has been set, or it has been invalidated by observation/target
+ * changes, Gaia will be searched for the best guide target available.
+ */
+ guideEnvironment: GuideEnvironment;
/** The guide star(s) and related information */
guideEnvironments: Array;
+ /**
+ * The name of the guide target, if any, set by `setGuideTargetName`.
+ * If the name is no longer valid or a sequence cannot be generated, null will
+ * be returned.
+ */
+ guideTargetName?: Maybe;
};
export type TargetEnvironmentAsterismArgs = {
@@ -6177,10 +6189,6 @@ export type TargetEnvironmentGuideAvailabilityArgs = {
start: Scalars['Timestamp']['input'];
};
-export type TargetEnvironmentGuideEnvironmentArgs = {
- lookupIfUndefined?: Scalars['Boolean']['input'];
-};
-
export type TargetEnvironmentGuideEnvironmentsArgs = {
observationTime: Scalars['Timestamp']['input'];
};
diff --git a/src/gql/render.tsx b/src/gql/render.tsx
index 212d00a..24d6f26 100644
--- a/src/gql/render.tsx
+++ b/src/gql/render.tsx
@@ -1,8 +1,8 @@
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
-import { render, RenderOptions } from '@testing-library/react';
import { createStore, Provider, WritableAtom } from 'jotai';
import { useHydrateAtoms } from 'jotai/utils';
import { PropsWithChildren, ReactElement } from 'react';
+import { ComponentRenderOptions, render } from 'vitest-browser-react';
import { GET_SLEW_FLAGS } from './configs/SlewFlags';
interface CreateOptions {
@@ -24,7 +24,7 @@ function HydrateAtoms({
export function renderWithContext(
ui: ReactElement,
createOptions: CreateOptions = {},
- options?: RenderOptions,
+ options?: ComponentRenderOptions,
) {
const store = createStore();
const renderResult = render(
diff --git a/src/gql/server/Buttons.tsx b/src/gql/server/Buttons.tsx
index 19a5e61..69a1015 100644
--- a/src/gql/server/Buttons.tsx
+++ b/src/gql/server/Buttons.tsx
@@ -2,10 +2,9 @@
import { useMutation, OperationVariables, DocumentNode } from '@apollo/client';
import { VariablesOf } from '@graphql-typed-document-node/core';
-import { useEffect, useRef } from 'react';
+import { useEffect } from 'react';
import { Button, ButtonProps } from 'primereact/button';
import { SlewFlagsType } from '@/types';
-import { Toast } from 'primereact/toast';
import { BTN_CLASSES } from '@/Helpers/constants';
import { graphql } from './gen';
import { Instrument, MechSystemState } from './gen/graphql';
@@ -17,6 +16,7 @@ import { useInstrument } from '@gql/configs/Instrument';
import { clsx } from 'clsx';
import { MOUNT_FOLLOW_MUTATION, OIWFS_FOLLOW_MUTATION, ROTATOR_FOLLOW_MUTATION, SCS_FOLLOW_MUTATION } from './follow';
import { ROTATOR_PARK_MUTATION, MOUNT_PARK_MUTATION, OIWFS_PARK_MUTATION } from './park';
+import { useToast } from '@/Helpers/toast';
// Generic mutation button
function MutationButton({
@@ -28,28 +28,23 @@ function MutationButton({
variables: VariablesOf extends OperationVariables ? VariablesOf : never;
} & ButtonProps) {
const TOAST_LIFE = 5000;
- const toast = useRef(null);
+ const toast = useToast();
const [mutationFunction, { loading, error }] = useMutation(mutation, {
variables: variables,
});
useEffect(() => {
if (error) {
- toast.current?.show({
+ toast?.show({
severity: 'error',
- summary: 'Error',
+ summary: error.name,
detail: error.message,
life: TOAST_LIFE,
});
}
}, [error]);
- return (
- <>
-
-