Skip to content

Commit

Permalink
Merge pull request #296 from AllenInstitute/feature/local_cloud_toggle
Browse files Browse the repository at this point in the history
feature/local_cloud_toggle
  • Loading branch information
BrianWhitneyAI authored Jan 3, 2025
2 parents f092db1 + 7dee6f8 commit ee945dc
Show file tree
Hide file tree
Showing 41 changed files with 961 additions and 301 deletions.
14 changes: 9 additions & 5 deletions packages/core/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import GlobalActionButtonRow from "./components/GlobalActionButtonRow";
import StatusMessage from "./components/StatusMessage";
import TutorialTooltip from "./components/TutorialTooltip";
import QuerySidebar from "./components/QuerySidebar";
import { FileExplorerServiceBaseUrl } from "./constants";
import { Environment } from "./constants";
import { interaction, selection } from "./state";
import useLayoutMeasurements from "./hooks/useLayoutMeasurements";

Expand All @@ -39,11 +39,11 @@ interface AppProps {
// Localhost: "https://localhost:9081"
// Stage: "http://stg-aics-api.corp.alleninstitute.org"
// From the web (behind load balancer): "/"
fileExplorerServiceBaseUrl?: string;
environment?: Environment;
}

export default function App(props: AppProps) {
const { fileExplorerServiceBaseUrl = FileExplorerServiceBaseUrl.PRODUCTION } = props;
const { environment = Environment.PRODUCTION } = props;

const dispatch = useDispatch();
const hasQuerySelected = useSelector(selection.selectors.hasQuerySelected);
Expand Down Expand Up @@ -79,8 +79,12 @@ export default function App(props: AppProps) {

// Set data source base urls
React.useEffect(() => {
dispatch(interaction.actions.initializeApp(fileExplorerServiceBaseUrl));
}, [dispatch, fileExplorerServiceBaseUrl]);
dispatch(
interaction.actions.initializeApp({
environment,
})
);
}, [dispatch, environment]);

// Respond to screen size changes
React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Annotation from "../../../entity/Annotation";
import FileFilter from "../../../entity/FileFilter";
import { initialState, reducer, reduxLogics, interaction, selection } from "../../../state";
import HttpAnnotationService from "../../../services/AnnotationService/HttpAnnotationService";
import { FESBaseUrl } from "../../../constants";

describe("<AnnotationFilterForm />", () => {
const LISTROW_TESTID_PREFIX = "default-button-";
Expand All @@ -31,14 +32,14 @@ describe("<AnnotationFilterForm />", () => {
it("shows all values as unchecked at first", async () => {
// arrange
const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: ["a", "b", "c", "d"] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});
sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService);
Expand Down Expand Up @@ -68,14 +69,14 @@ describe("<AnnotationFilterForm />", () => {
it("deselects and selects a value", async () => {
// arrange
const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: ["a", "b", "c", "d"] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});
sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService);
Expand Down Expand Up @@ -124,14 +125,14 @@ describe("<AnnotationFilterForm />", () => {
it("naturally sorts values", async () => {
// arrange
const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: ["AICS-24", "AICS-0", "aics-32", "aICs-2"] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});
sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService);
Expand Down Expand Up @@ -172,14 +173,14 @@ describe("<AnnotationFilterForm />", () => {
});

const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: [true, false] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});

Expand Down Expand Up @@ -279,14 +280,14 @@ describe("<AnnotationFilterForm />", () => {
it("naturally sorts values", async () => {
// arrange
const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: [5, 8, 6.3, -12, 10000000000, 0] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});
sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService);
Expand Down Expand Up @@ -334,14 +335,14 @@ describe("<AnnotationFilterForm />", () => {
it("naturally sorts values", async () => {
// arrange
const responseStub = {
when: `test/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
when: `${FESBaseUrl.TEST}/file-explorer-service/1.0/annotations/${fooAnnotation.name}/values`,
respondWith: {
data: { data: [446582220, 125, 10845000, 86400000] },
},
};
const mockHttpClient = createMockHttpClient(responseStub);
const annotationService = new HttpAnnotationService({
baseUrl: "test",
fileExplorerServiceBaseUrl: FESBaseUrl.TEST,
httpClient: mockHttpClient,
});
sandbox.stub(interaction.selectors, "getAnnotationService").returns(annotationService);
Expand Down
17 changes: 7 additions & 10 deletions packages/core/components/DirectoryTree/test/DirectoryTree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { Provider } from "react-redux";
import { createSandbox } from "sinon";

import { TOP_LEVEL_FILE_ANNOTATIONS } from "../../../constants";
import { FESBaseUrl, TOP_LEVEL_FILE_ANNOTATIONS } from "../../../constants";
import Annotation from "../../../entity/Annotation";
import AnnotationName from "../../../entity/Annotation/AnnotationName";
import { FmsFileAnnotation } from "../../../services/FileService";
Expand Down Expand Up @@ -53,17 +53,13 @@ describe("<DirectoryTree />", () => {
type: "Text",
});

const baseUrl = "http://test-aics.corp.alleninstitute.org";
const baseDisplayAnnotations = TOP_LEVEL_FILE_ANNOTATIONS.filter(
(a) => a.name === AnnotationName.FILE_NAME
);
const state = mergeState(initialState, {
metadata: {
annotations: [...baseDisplayAnnotations, fooAnnotation, barAnnotation],
},
interaction: {
fileExplorerServiceBaseUrl: baseUrl,
},
selection: {
annotationHierarchy: [fooAnnotation.name, barAnnotation.name],
columns: [...baseDisplayAnnotations, fooAnnotation, barAnnotation].map((a) => ({
Expand Down Expand Up @@ -194,9 +190,13 @@ describe("<DirectoryTree />", () => {
},
];
const mockHttpClient = createMockHttpClient(responseStubs);
const annotationService = new HttpAnnotationService({ baseUrl, httpClient: mockHttpClient });
const fileExplorerServiceBaseUrl = FESBaseUrl.TEST;
const annotationService = new HttpAnnotationService({
fileExplorerServiceBaseUrl: fileExplorerServiceBaseUrl,
httpClient: mockHttpClient,
});
const fileService = new HttpFileService({
baseUrl,
fileExplorerServiceBaseUrl: fileExplorerServiceBaseUrl,
httpClient: mockHttpClient,
downloadService: new FileDownloadServiceNoop(),
});
Expand Down Expand Up @@ -362,9 +362,6 @@ describe("<DirectoryTree />", () => {
metadata: {
annotations: [...baseDisplayAnnotations, fooAnnotation, barAnnotation],
},
interaction: {
fileExplorerServiceBaseUrl: baseUrl,
},
selection: {
annotationHierarchy: [fooAnnotation.name],
columns: [...baseDisplayAnnotations, fooAnnotation, barAnnotation].map((a) => ({
Expand Down
61 changes: 26 additions & 35 deletions packages/core/components/FileDetails/FileAnnotationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ export default function FileAnnotationList(props: FileAnnotationListProps) {
return;
}

const path = await executionEnvService.formatPathForHost(fileDetails.path);
let path;
if (fileDetails.localPath === null) {
// The Local File Path annotation is not defined because the file is not available
// on-premises
path = fileDetails.localPath;
} else {
path = await executionEnvService.formatPathForHost(fileDetails.localPath);
}
if (!active) {
return;
}
Expand Down Expand Up @@ -65,13 +72,29 @@ export default function FileAnnotationList(props: FileAnnotationListProps) {
return accum;
}

const annotationValue = annotation.extractFromFile(fileDetails);
let annotationValue = annotation.extractFromFile(fileDetails);
if (annotationValue === Annotation.MISSING_VALUE) {
// Nothing to show for this annotation -- skip
return accum;
}

const ret = [
if (annotation.name === AnnotationName.LOCAL_FILE_PATH) {
if (localPath === null) {
// localPath hasn't loaded yet, but it should eventually because there is an
// annotation named AnnotationName.LOCAL_FILE_PATH
return accum;
} else {
// Use the user's /allen mount point, if known
annotationValue = localPath;
}
}

if (annotation.name === AnnotationName.FILE_PATH) {
// Display the full http://... URL
annotationValue = fileDetails.cloudPath;
}

return [
...accum,
<FileAnnotationRow
key={annotation.displayName}
Expand All @@ -80,38 +103,6 @@ export default function FileAnnotationList(props: FileAnnotationListProps) {
value={annotationValue}
/>,
];

// Special case for file paths: we want to display both the "canonical" FMS path
// (i.e. POSIX path held in the database; what we have an annotation for)
// as well as the path at which the file is *actually* accessible on _this_ computer ("local" file path)
if (annotation.name === AnnotationName.FILE_PATH) {
if (fileDetails.path !== fileDetails.cloudPath) {
ret.push(
<FileAnnotationRow
key="file-path-cloud"
className={styles.row}
name="File Path (Cloud)"
value={fileDetails.cloudPath}
/>
);
}

// In certain circumstances (i.e., linux), the path at which a file is accessible is === the canonical path
if (localPath && localPath !== annotationValue && !localPath.startsWith("http")) {
ret.splice(
-1, // Insert before the "canonical" path so that it is the first path-like row to be seen
0, // ...don't delete the "canonical" path
<FileAnnotationRow
key="file-path-local"
className={styles.row}
name="File Path (Local)"
value={localPath}
/>
);
}
}

return ret;
}, [] as JSX.Element[]);
}, [annotations, fileDetails, isLoading, localPath]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import FileDetail from "../../../entity/FileDetail";
import ExecutionEnvServiceNoop from "../../../services/ExecutionEnvService/ExecutionEnvServiceNoop";
import { initialState } from "../../../state";
import { TOP_LEVEL_FILE_ANNOTATIONS } from "../../../constants";
import Annotation from "../../../entity/Annotation";
import { AnnotationType } from "../../../entity/AnnotationFormatter";

describe("<FileAnnotationList />", () => {
describe("file path representation", () => {
it("has both canonical file path and file path adjusted to OS & allen mount point", async () => {
it("has both cloud file path and local file path adjusted to OS & allen mount point", async () => {
// Arrange
const hostMountPoint = "/some/path";

Expand All @@ -24,6 +26,17 @@ describe("<FileAnnotationList />", () => {

const { store } = configureMockStore({
state: mergeState(initialState, {
metadata: {
annotations: [
...TOP_LEVEL_FILE_ANNOTATIONS,
new Annotation({
annotationName: "Local File Path",
annotationDisplayName: "File Path (Local VAST)",
description: "Path to file in on-premises storage.",
type: AnnotationType.STRING,
}),
],
},
interaction: {
platformDependentServices: {
executionEnvService: new FakeExecutionEnvService(),
Expand All @@ -33,19 +46,18 @@ describe("<FileAnnotationList />", () => {
});

const filePathInsideAllenDrive = "path/to/MyFile.txt";

const canonicalFilePath = `/allen/${filePathInsideAllenDrive}`;
const filePath = `production.files.allencell.org/${filePathInsideAllenDrive}`;
const fileDetails = new FileDetail({
file_path: canonicalFilePath,
file_path: filePath,
file_id: "abc123",
file_name: "MyFile.txt",
file_size: 7,
uploaded: "01/01/01",
annotations: [],
annotations: [
{ name: "Local File Path", values: [`/allen/${filePathInsideAllenDrive}`] },
],
});

const expectedLocalPath = `${hostMountPoint}/${filePathInsideAllenDrive}`;

// Act
const { findByText } = render(
<Provider store={store}>
Expand All @@ -54,17 +66,17 @@ describe("<FileAnnotationList />", () => {
);

// Assert
[
"File Path (Canonical)",
canonicalFilePath,
"File Path (Local)",
expectedLocalPath,
].forEach(async (cellText) => {
for (const cellText of [
"File Path (Cloud)",
`https://s3.us-west-2.amazonaws.com/${filePath}`,
"File Path (Local VAST)",
`${hostMountPoint}/${filePathInsideAllenDrive}`,
]) {
expect(await findByText(cellText)).to.not.be.undefined;
});
}
});

it("has only canonical file path when no allen mount point is found", () => {
it("has only cloud file path when no allen mount point is found", () => {
// Arrange
class FakeExecutionEnvService extends ExecutionEnvServiceNoop {
public formatPathForHost(posixPath: string): Promise<string> {
Expand All @@ -86,7 +98,7 @@ describe("<FileAnnotationList />", () => {
});

const filePathInsideAllenDrive = "path/to/MyFile.txt";
const filePath = `/allen/${filePathInsideAllenDrive}`;
const filePath = `production.files.allencell.org/${filePathInsideAllenDrive}`;
const fileDetails = new FileDetail({
file_path: filePath,
file_id: "abc123",
Expand All @@ -104,10 +116,12 @@ describe("<FileAnnotationList />", () => {
);

// Assert
expect(() => getByText("File Path (Local)")).to.throw();
["File Path (Canonical)", filePath].forEach((cellText) => {
expect(getByText(cellText)).to.not.be.undefined;
});
expect(() => getByText("File Path (Local VAST)")).to.throw();
["File Path (Cloud)", `https://s3.us-west-2.amazonaws.com/${filePath}`].forEach(
(cellText) => {
expect(getByText(cellText)).to.not.be.undefined;
}
);
});
});
});
Loading

0 comments on commit ee945dc

Please sign in to comment.