Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions packages/components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@labkey/components",
"version": "7.58.5",
"version": "7.58.6-fb-issue1470.1",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
11 changes: 11 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,12 @@ import { EditInlineField } from './internal/components/EditInlineField';
import { FileAttachmentArea } from './internal/components/files/FileAttachmentArea';
import { Discussions } from './internal/announcements/Discussions';
import { useModalState, useNotAuthorized, useNotFound, usePortalRef, useTimeout } from './internal/hooks';
import {
useCanonicalQueryName,
UseCanonicalQueryName,
useSampleTypeParam,
useSourceTypeParam,
} from './internal/components/entities/useEntityTypeParam';
import {
TEST_BIO_LIMS_ENTERPRISE_MODULE_CONTEXT,
TEST_BIO_LIMS_STARTER_MODULE_CONTEXT,
Expand Down Expand Up @@ -1105,6 +1111,7 @@ const App = {

const Hooks = {
useAppContext,
useCanonicalQueryName,
useContainerPath,
useContainerUser,
useEnterEscape,
Expand Down Expand Up @@ -1781,6 +1788,7 @@ export {
useAdministrationSubNav,
useAppContext,
useAppNavigate,
useCanonicalQueryName,
useContainerPath,
useContainerUser,
useDataChangeCommentsRequired,
Expand Down Expand Up @@ -1809,8 +1817,10 @@ export {
UserProfile,
UserSelectInput,
UsersGridPanel,
useSampleTypeParam,
useServerContext,
useServerContextDispatch,
useSourceTypeParam,
useSubNavTabsContext,
useTimeout,
useUserProperties,
Expand Down Expand Up @@ -1935,6 +1945,7 @@ export type { IDataViewInfo } from './internal/DataViewInfo';
export type { BSStyle } from './internal/dropdowns';
export type { MenuSectionItem } from './internal/DropdownSection';
export type { UseTimeout } from './internal/hooks';
export type { UseCanonicalQueryName } from './internal/components/entities/useEntityTypeParam';
export type { ModalProps } from './internal/Modal';
export type { AddEntitiesComplete, ModalRendererProps } from './internal/ModalRenderFactory';
export type { TriggerType } from './internal/OverlayTrigger';
Expand Down
10 changes: 5 additions & 5 deletions packages/components/src/internal/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ export function saveGridView(
containerPath,
views: [{ ...ViewInfo.serialize(viewInfo), replace, session, inherit, shared, hidden }],
success: () => {
invalidateQueryDetailsCache(schemaQuery, containerPath);
invalidateQueryDetailsCache(schemaQuery);
resolve();
},
failure: response => {
Expand Down Expand Up @@ -640,7 +640,7 @@ export function saveSessionView(
hidden: false,
replace,
success: () => {
invalidateQueryDetailsCache(schemaQuery, containerPath);
invalidateQueryDetailsCache(schemaQuery);
resolve();
},
failure: response => {
Expand Down Expand Up @@ -720,12 +720,12 @@ export function deleteView(
containerPath,
revert,
success: () => {
invalidateQueryDetailsCache(schemaQuery, containerPath);
invalidateQueryDetailsCache(schemaQuery);
resolve();
},
failure: response => {
if (response.exceptionClass === VIEW_NOT_FOUND_EXCEPTION_CLASS) {
invalidateQueryDetailsCache(schemaQuery, containerPath);
invalidateQueryDetailsCache(schemaQuery);
resolve(); // view has already been deleted
} else {
console.error(response);
Expand Down Expand Up @@ -760,7 +760,7 @@ export function renameGridView(
newName,
},
success: Utils.getCallbackWrapper(response => {
invalidateQueryDetailsCache(schemaQuery, containerPath);
invalidateQueryDetailsCache(schemaQuery);
resolve();
}),
failure: Utils.getCallbackWrapper(error => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2026 LabKey Corporation. All rights reserved. No portion of this work may be reproduced in
* any form or by any electronic or mechanical means without written permission from LabKey Corporation.
*/
import { useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router';

import { isLoading, LoadingState } from '../../../public/LoadingState';
import { QueryInfo } from '../../../public/QueryInfo';
import { SchemaQuery } from '../../../public/SchemaQuery';
import { useAppContext } from '../../AppContext';
import { resolveErrorMessage } from '../../util/messaging';
import { SCHEMAS } from '../../schemas';

export interface UseCanonicalQueryName {
error?: string;
isLoaded: boolean;
notFound: boolean;
queryInfo?: QueryInfo;
queryName?: string;
schemaName?: string;
schemaQuery?: SchemaQuery;
}

/**
* Resolves a possibly wrong-case schema/query name (e.g. a route param) to the server's canonical case via the cached getQueryDetails.
* `notFound` is set when the type does not exist, to drive a NotFound page.
*/
export function useCanonicalQueryName(
schemaName: string,
rawQueryName: string,
containerPath?: string
): UseCanonicalQueryName {
const { api } = useAppContext();
const [queryInfo, setQueryInfo] = useState<QueryInfo>();
const [error, setError] = useState<string>();
const [notFound, setNotFound] = useState(false);
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.INITIALIZED);

useEffect(() => {
setQueryInfo(undefined);
setError(undefined);
setNotFound(false);

if (!schemaName || !rawQueryName) return;

(async () => {
setLoadingState(LoadingState.LOADING);
try {
const queryInfo_ = await api.query.getQueryDetails({
schemaName,
queryName: rawQueryName,
containerPath,
});
setQueryInfo(queryInfo_);
} catch (e) {
setNotFound(true);
setError(resolveErrorMessage(e));
} finally {
setLoadingState(LoadingState.LOADED);
}
})();
}, [api, schemaName, rawQueryName, containerPath]);

return useMemo(
() => ({
error,
isLoaded: !isLoading(loadingState),
notFound,
queryInfo,
queryName: queryInfo?.name,
schemaName: queryInfo?.schemaQuery?.schemaName,
schemaQuery: queryInfo?.schemaQuery,
}),
[error, loadingState, notFound, queryInfo]
);
}

export function useSampleTypeParam(containerPath?: string): UseCanonicalQueryName {
const { sampleType } = useParams();
return useCanonicalQueryName(SCHEMAS.SAMPLE_SETS.SCHEMA, sampleType, containerPath);
}

export function useSourceTypeParam(entityTypeKey = 'sourceType', containerPath?: string): UseCanonicalQueryName {
const params = useParams();
return useCanonicalQueryName(SCHEMAS.DATA_CLASSES.SCHEMA, params[entityTypeKey], containerPath);
}
25 changes: 8 additions & 17 deletions packages/components/src/internal/query/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,13 @@ function getQueryDetailsCacheKey(

export function invalidateQueryDetailsCache(
schemaQuery: SchemaQuery,
containerPath?: string,
fk?: string,
fields?: string | string[],
exactKeyMatch = false
): void {
if (exactKeyMatch) {
const key = getQueryDetailsCacheKey(schemaQuery, containerPath, fk, fields);
delete queryDetailsCache[key];
} else {
const prefix = getQueryDetailsCacheKey(schemaQuery);
Object.keys(queryDetailsCache).forEach(cacheKey => {
if (cacheKey.startsWith(prefix)) {
delete queryDetailsCache[cacheKey];
}
});
}
const prefix = getQueryDetailsCacheKey(schemaQuery);
Object.keys(queryDetailsCache).forEach(cacheKey => {
if (cacheKey.toLowerCase().startsWith(prefix.toLowerCase())) {
delete queryDetailsCache[cacheKey];
}
});
}

interface GetQueryDetailsBasic extends Omit<
Expand Down Expand Up @@ -127,7 +118,7 @@ export function getQueryDetails(options: GetQueryDetailsOptions): Promise<QueryI
// where it is unable to resolve the tableInfo. This is deemed a 'success'
// by the request standards but here we reject as an outright failure
if (queryDetails.exception) {
invalidateQueryDetailsCache(schemaQuery, containerPath, fk, fields);
invalidateQueryDetailsCache(schemaQuery);
reject({
schemaQuery,
message: queryDetails.exception,
Expand All @@ -141,7 +132,7 @@ export function getQueryDetails(options: GetQueryDetailsOptions): Promise<QueryI
},
failure: (error, request) => {
console.error(error);
invalidateQueryDetailsCache(schemaQuery, containerPath, fk, fields);
invalidateQueryDetailsCache(schemaQuery);
reject({
message: error.exception,
exceptionClass: error.exceptionClass,
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/public/QueryModel/QueryModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,11 @@ export class QueryModel {
}

get schemaName(): string {
return this.schemaQuery.schemaName;
return (this.queryInfo?.schemaQuery ?? this.schemaQuery).schemaName;
}

get queryName(): string {
return this.schemaQuery.queryName;
return (this.queryInfo?.schemaQuery ?? this.schemaQuery).queryName;
}

get viewName(): string {
Expand Down