Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X1244 number stored items #777

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cypress/e2e/pages/store.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ describe('Store', () => {
context('when the location is found', () => {
before(() => {
cy.visit('/store');
cy.findByLabelText('Find Location:').should('be.visible').type('STO-001F{enter}');
cy.findByLabelText('Find Location:').should('be.visible').type('STO-001{enter}');
});

it("takes you to that location's page", () => {
cy.location('pathname').should('eq', '/locations/STO-001F');
cy.findByText('Location STO-001F could not be found').should('not.exist');
cy.location('pathname').should('eq', '/locations/STO-001');
cy.findByText('Location STO-001 could not be found').should('not.exist');
});
});

Expand Down
21 changes: 16 additions & 5 deletions src/components/RouteLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { CellSegmentationQc } from '../pages/CellSegmentationQc';
import { CellSegmentation } from '../pages/CellSegmentation';
import CleanOut from '../pages/CleanOut';
import XeniumMetrics from '../pages/XeniumMetrics';
import { LocationFamily } from '../lib/machines/locations/locationMachineTypes';

const RouteLayout = () => {
const stanCore = useContext(StanCoreContext);
Expand Down Expand Up @@ -487,12 +488,22 @@ const RouteLayout = () => {
<Route
path="/locations/:locationBarcode"
loader={async ({ params }) => {
// the matching param will be available to the loader
const locationFamily = {} as LocationFamily;
if (params.locationBarcode) {
const res = await stanCore.FindLocationByBarcode({
barcode: params.locationBarcode
});
return res.location;
await stanCore
.FindLocationByBarcode({
barcode: params.locationBarcode
})
.then(async (parent) => {
sabrine33 marked this conversation as resolved.
Show resolved Hide resolved
locationFamily.parent = parent.location;
locationFamily.children = [];
for (const child of parent.location.children) {
await stanCore.FindLocationByBarcode({ barcode: child.barcode }).then((res) => {
locationFamily.children.push(res.location);
});
}
});
return locationFamily;
}
}}
element={<Location />}
Expand Down
1 change: 1 addition & 0 deletions src/graphql/fragments/LinkedLocationFields.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ fragment LinkedLocationFields on LinkedLocation {
fixedName
customName
address
numStored
}
6 changes: 2 additions & 4 deletions src/graphql/fragments/LocationFields.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ fragment LocationFields on Location {
customName
address
direction
numStored
parent {
barcode
fixedName
Expand All @@ -18,9 +19,6 @@ fragment LocationFields on Location {
address
}
children {
barcode
fixedName
customName
address
...LinkedLocationFields
}
}
6 changes: 4 additions & 2 deletions src/lib/factories/locationFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const locationFactory = Factory.define<Location>(({ sequence, params, afterBuild
size: null,
direction: params.direction ?? null,
parent: params.parent == null ? null : (params.parent as LinkedLocation),
qualifiedNameWithFirstBarcode: params.qualifiedNameWithFirstBarcode ?? `FakeParent / ${barcode}`
qualifiedNameWithFirstBarcode: params.qualifiedNameWithFirstBarcode ?? `FakeParent / ${barcode}`,
numStored: params.numStored ?? 0
};

if (params.size) {
Expand Down Expand Up @@ -55,6 +56,7 @@ export function buildLinkedLocation(location: Location): LinkedLocation {
barcode: location.barcode,
fixedName: location.fixedName,
customName: location.customName,
address: location.address
address: location.address,
numStored: location.numStored
};
}
70 changes: 44 additions & 26 deletions src/lib/machines/locations/locationMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import {
LocationEvent,
setErrorMessage,
setSuccessMessage,
StoreBarcodeEvent
StoreBarcodeEvent,
StoredItemFragment
} from './locationMachineTypes';
import * as locationService from '../../services/locationService';
import { castDraft, produce } from '../../../dependencies/immer';
import { stanCore } from '../../sdk';
import { GridDirection, LocationFieldsFragment } from '../../../types/sdk';
import { buildOrderedAddresses, findNextAvailableAddress } from '../../helpers/locationHelper';
import { GridDirection } from '../../../types/sdk';

enum Action {
ASSIGN_LOCATION = 'assignLocation',
Expand All @@ -19,7 +20,8 @@ enum Action {
UNSET_SUCCESS_MESSAGE = 'unsetSuccessMessage',
ASSIGN_SUCCESS_MESSAGE = 'assignSuccessMessage',
ASSIGN_ERROR_MESSAGE = 'assignErrorMessage',
ASSIGN_SERVER_ERRORS = 'assignServerErrors'
ASSIGN_SERVER_ERRORS = 'assignServerErrors',
UPDATE_PARENT_LEAF_MAP = 'updateParentLeafMap'
}

enum Service {
Expand Down Expand Up @@ -57,29 +59,26 @@ export const machineOptions: MachineImplementations<LocationContext, LocationEve
if (event.type !== 'UPDATE_LOCATION' && event.output == null) {
return context;
}
return produce(context, (draft) => {
// Set the location
draft.location = event.type === 'UPDATE_LOCATION' ? event.location : event.output;

draft.addressToItemMap.clear();
// Create all the possible addresses for this location if it has a size.
draft.locationAddresses = draft.location.size
? buildOrderedAddresses(draft.location.size, draft.location.direction ?? GridDirection.DownRight)
: new Map<string, number>();

draft.location.stored.forEach((storedItem) => {
if (storedItem.address) {
draft.addressToItemMap.set(storedItem.address, storedItem);
}
});
const location = event.type === 'UPDATE_LOCATION' ? event.location : event.output;
const addressToItemMap = new Map<string, StoredItemFragment>();
// Create all the possible addresses for this location if it has a size.
const locationAddresses = location.size
? buildOrderedAddresses(location.size, location.direction ?? GridDirection.DownRight)
: new Map<string, number>();

const addresses = findNextAvailableAddress({
locationAddresses: draft.locationAddresses,
addressToItemMap: draft.addressToItemMap,
minimumAddress: draft.selectedAddress
});
draft.selectedAddress = addresses.length > 0 ? addresses[0] : null;
location.stored.forEach((storedItem) => {
if (storedItem.address) {
addressToItemMap.set(storedItem.address, storedItem);
}
});
const addresses = findNextAvailableAddress({
locationAddresses,
addressToItemMap,
minimumAddress: context.selectedAddress
});
const selectedAddress = addresses.length > 0 ? addresses[0] : null;
return { ...context, location, addressToItemMap, locationAddresses, selectedAddress };
}),

[Action.ASSIGN_SELECTED_ADDRESS]: assign(({ context, event }) => {
Expand Down Expand Up @@ -118,16 +117,34 @@ export const machineOptions: MachineImplementations<LocationContext, LocationEve
return context;
}
return { ...context, serverError: castDraft(event.error) };
}),
[Action.UPDATE_PARENT_LEAF_MAP]: assign(({ context, event }) => {
if (event.type !== 'UPDATE_PARENT_LEAF_MAP') {
return context;
}
return produce(context, (draft) => {
draft.parentLeafMap.set(event.locationFamily.parent.barcode, event.locationFamily.children.length === 0);
event.locationFamily.children.forEach((location: LocationFieldsFragment) => {
draft.parentLeafMap.set(location.barcode, location.children.length === 0);
});
});
})
}
};

export const machineConfig: MachineConfig<LocationContext, LocationEvent> = {
id: 'locations',
initial: 'ready',
context: ({ input }) => ({
...input
}),
context: ({ input }) => {
const parentLeafMap = new Map(
input.locationFamily.children.map((child: LocationFieldsFragment) => [child.barcode, child.children.length === 0])
);
parentLeafMap.set(input.locationFamily.parent.barcode, input.locationFamily.children.length === 0);
return {
...input,
parentLeafMap
};
},
states: {
fetching: {
invoke: {
Expand All @@ -150,6 +167,7 @@ export const machineConfig: MachineConfig<LocationContext, LocationEvent> = {
},
ready: {
on: {
UPDATE_PARENT_LEAF_MAP: { actions: Action.UPDATE_PARENT_LEAF_MAP },
FETCH_LOCATION: 'fetching',
UPDATE_LOCATION: { actions: Action.ASSIGN_LOCATION },
STORE_BARCODE: 'updating.storingBarcode',
Expand Down
18 changes: 17 additions & 1 deletion src/lib/machines/locations/locationMachineTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { LocationFieldsFragment, Maybe, StoreInput } from '../../../types/sdk';
import { ClientError } from 'graphql-request';
import { LocationSearchParams } from '../../../types/stan';

export type LocationFamily = {
parent: LocationFieldsFragment;
children: LocationFieldsFragment[];
};

/**
* Context for a Location Machine
*/
Expand Down Expand Up @@ -46,6 +51,11 @@ export interface LocationContext {
* Error that's come back from a request to core
*/
serverError: Maybe<ClientError>;

/** State to keep track of whether each child location is a parent of a leaf node (has no children) */
parentLeafMap: Map<string, boolean>;

locationFamily: LocationFamily;
}

/**
Expand Down Expand Up @@ -166,6 +176,11 @@ type StoreErrorEvent = {
error: ClientError;
};

type UpdateParentLeafMapEvent = {
type: 'UPDATE_PARENT_LEAF_MAP';
locationFamily: LocationFamily;
};

export type LocationEvent =
| FetchLocationEvent
| FetchLocationResolveEvent
Expand All @@ -185,7 +200,8 @@ export type LocationEvent =
| SetErrorMessageEvent
| StoreEvent
| StoreResolveEvent
| StoreErrorEvent;
| StoreErrorEvent
| UpdateParentLeafMapEvent;

/**
* The type of an interpreted Location Machine
Expand Down
7 changes: 5 additions & 2 deletions src/mocks/handlers/locationHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ function addLocationHierarchy(barcode: string, linkedLocation: LinkedLocationFie
barcode: location.barcode,
address: location.address,
customName: location.customName,
fixedName: location.fixedName
fixedName: location.fixedName,
numStored: location.numStored
});
if (location.parent) {
addLocationHierarchy(location.parent.barcode, linkedLocation);
Expand All @@ -185,6 +186,7 @@ export function locationResponse(location: Location): LocationFieldsFragment {
customName: location.customName,
address: location.address,
direction: location.direction,
numStored: location.numStored,
parent: location.parent
? {
__typename: 'LinkedLocation',
Expand All @@ -211,7 +213,8 @@ export function locationResponse(location: Location): LocationFieldsFragment {
barcode: child.barcode,
fixedName: child.fixedName,
customName: child.customName,
address: child.address
address: child.address,
numStored: child.numStored
};
})
};
Expand Down
55 changes: 38 additions & 17 deletions src/pages/Location.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Authenticated, Unauthenticated } from '../components/Authenticated';
import { extractServerErrors } from '../types/stan';
import { GridDirection, LocationFieldsFragment, Maybe, StoreInput, UserRole } from '../types/sdk';
import { useMachine } from '@xstate/react';
import { StoredItemFragment } from '../lib/machines/locations/locationMachineTypes';
import { LocationFamily, StoredItemFragment } from '../lib/machines/locations/locationMachineTypes';
import { locationMachine } from '../lib/machines/locations/locationMachine';
import { awaitingStorageCheckOnExit, getAwaitingLabwaresFromSession, LabwareAwaitingStorageInfo } from './Store';
import LabwareAwaitingStorage from './location/LabwareAwaitingStorage';
Expand Down Expand Up @@ -63,7 +63,8 @@ export type LocationParentContextType = {
export const LocationParentContext = React.createContext<Maybe<LocationParentContextType>>(null);

const Location = () => {
const storageLocation = useLoaderData() as LocationFieldsFragment;
const locationFamily = useLoaderData() as LocationFamily;
const storageLocation = locationFamily.parent;
const [searchParams] = useSearchParams();
const memoLabwareBarcode = React.useMemo(() => searchParams.get('labwareBarcode') ?? '', [searchParams]);
const memoLocationMachineParams = React.useMemo(() => {
Expand Down Expand Up @@ -102,11 +103,20 @@ const Location = () => {
...memoLocationMachineParams,
successMessage: '',
errorMessage: '',
serverError: null
serverError: null,
locationFamily
}
});
const { location, locationAddresses, successMessage, errorMessage, serverError, addressToItemMap, selectedAddress } =
current.context;
const {
location,
locationAddresses,
successMessage,
errorMessage,
serverError,
addressToItemMap,
selectedAddress,
parentLeafMap
} = current.context;

const locationHasGrid = !!location.size;

Expand All @@ -115,6 +125,14 @@ const Location = () => {
send({ type: 'UPDATE_LOCATION', location: storageLocation });
}, [send, storageLocation, location.barcode]);

React.useEffect(() => {
if (
locationFamily.parent.barcode === current.context.locationFamily.parent.barcode &&
locationFamily.children.length === current.context.locationFamily.children.length
)
return;
send({ type: 'UPDATE_PARENT_LEAF_MAP', locationFamily: locationFamily });
}, [send, locationFamily, current.context.locationFamily]);
/**
* Should the page be displaying the grid or list view of the items
*/
Expand Down Expand Up @@ -495,18 +513,21 @@ const Location = () => {
{location.children.length > 0 && (
<StripyCardDetail term={'Children'} dataTestId={'location-children'}>
<ul className="list-disc list-inside">
{location.children.map((child) => {
return (
<li key={child.barcode}>
<StyledLink
to={`/locations/${child.barcode}`}
state={awaitingLabwares ? { awaitingLabwares: awaitingLabwares } : {}}
>
{child.customName ?? child.fixedName ?? child.barcode}
</StyledLink>
</li>
);
})}
{location.children.map((child) => (
<li key={child.barcode}>
<StyledLink
to={`/locations/${child.barcode}`}
state={awaitingLabwares ? { awaitingLabwares: awaitingLabwares } : {}}
>
{child.customName ?? child.fixedName ?? child.barcode}
</StyledLink>
{parentLeafMap.get(child.barcode) === true && (
<span className="font-semibold" data-testid={'storedItemsCount'}>
{` - Number of Stored items : ${child.numStored}`}
</span>
)}
</li>
))}
</ul>
</StripyCardDetail>
)}
Expand Down
Loading
Loading