Skip to content
Merged
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
8 changes: 5 additions & 3 deletions src/components/BeamlineSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FileContext, executeAction } from "@diamondlightsource/cs-web-lib";
import { CHANGE_BEAMLINE } from "../store";
import { Tooltip } from "@mui/material";
import { BeamlineTreeStateContext } from "../App";
import { buildUrl } from "../utils/urlUtils";

const MenuItem = styled(MuiMenuItem)({
"&.Mui-disabled": {
Expand All @@ -32,9 +33,10 @@ export default function BeamlineSelect() {
location: "main",
description: undefined,
file: {
path:
state.beamlines[event.target.value].host +
state.beamlines[event.target.value].topLevelScreen,
path: buildUrl(
state.beamlines[event.target.value].host,
state.beamlines[event.target.value].topLevelScreen
),
macros: {},
defaultProtocol: "ca"
}
Expand Down
11 changes: 8 additions & 3 deletions src/components/ScreenTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TreeViewBaseItem, TreeViewItemId } from "@mui/x-tree-view";
import { executeAction, FileContext } from "@diamondlightsource/cs-web-lib";
import { BeamlineTreeStateContext } from "../App";
import { MenuContext } from "../routes/MainPage";
import { buildUrl } from "../utils/urlUtils";

export default function ScreenTreeView() {
const { state } = useContext(BeamlineTreeStateContext);
Expand All @@ -16,9 +17,13 @@ export default function ScreenTreeView() {
};

const handleClick = (itemId: string) => {
const newScreen =
state.beamlines[state.currentBeamline].host +
state.beamlines[state.currentBeamline].filePathIds[itemId].file;
const fileMetadata =
state.beamlines[state.currentBeamline].filePathIds[itemId];
const newScreen = buildUrl(
state.beamlines[state.currentBeamline].host,
fileMetadata.file
);

executeAction(
{
type: "OPEN_PAGE",
Expand Down
10 changes: 5 additions & 5 deletions src/components/SynopticBreadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Breadcrumbs, Link } from "@mui/material";
import { executeAction, FileContext } from "@diamondlightsource/cs-web-lib";
import { BeamlineStateProperties } from "../store";
import { BeamlineTreeStateContext } from "../App";
import { buildUrl } from "../utils/urlUtils";

export const SynopticBreadcrumbs = () => {
const { state } = useContext(BeamlineTreeStateContext);
Expand Down Expand Up @@ -44,12 +45,11 @@ const handleClick =
.split("/")
.at(-1) as string;

const filepath =
Object.values(currentBeamlineState.filePathIds).find(
x => x.urlId === urlId
)?.file ?? "";
const fileMetadata = Object.values(currentBeamlineState.filePathIds).find(
x => x.urlId === urlId
);
const newScreen = buildUrl(currentBeamlineState.host, fileMetadata?.file);

const newScreen = currentBeamlineState.host + filepath;
executeAction(
{
type: "OPEN_PAGE",
Expand Down
3 changes: 2 additions & 1 deletion src/routes/EditorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { parseScreenTree } from "../utils/parser";
import { executeAction, FileContext } from "@diamondlightsource/cs-web-lib";
import Editor from "../components/Editor";
import { useParams } from "react-router-dom";
import { buildUrl } from "../utils/urlUtils";

/**
* Displays a mock editor page with palette and Phoebus
Expand Down Expand Up @@ -63,7 +64,7 @@ export function EditorPage() {
const newBeamlineState = newBeamlines[params.beamline];
const newScreen = params.screenUrlId
? newBeamlineState.filePathIds[params.screenUrlId].file
: newBeamlineState.host + newBeamlineState.topLevelScreen;
: buildUrl(newBeamlineState.host, newBeamlineState.topLevelScreen);
executeAction(
{
type: "OPEN_PAGE",
Expand Down
9 changes: 6 additions & 3 deletions src/routes/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { RotatingLines } from "react-loader-spinner";
import { SynopticBreadcrumbs } from "../components/SynopticBreadcrumbs";
import { BeamlineTreeStateContext } from "../App";
import { useParams } from "react-router-dom";
import { buildUrl } from "../utils/urlUtils";

export const MenuContext = createContext<{
menuOpen: boolean;
Expand Down Expand Up @@ -54,7 +55,7 @@ export function MainPage() {
for (const [beamline, item] of Object.entries(newBeamlines)) {
try {
const [tree, fileIDs, firstFile] = await parseScreenTree(
item.host + item.entryPoint
buildUrl(item.host, item.entryPoint)
);
item.screenTree = tree;
item.filePathIds = fileIDs;
Expand Down Expand Up @@ -82,8 +83,10 @@ export function MainPage() {
x => x.urlId === params.screenUrlId
)?.file;

const newScreen =
newBeamlineState.host + (filepath ?? newBeamlineState.topLevelScreen);
const newScreen = buildUrl(
newBeamlineState.host,
filepath ?? newBeamlineState.topLevelScreen
);

executeAction(
{
Expand Down
135 changes: 135 additions & 0 deletions src/tests/utils/urlUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { describe, expect, it } from "vitest";
import { buildUrl, isFullyQualifiedUrl } from "../../utils/urlUtils";

describe("urlUtils", () => {
describe("isFullyQualifiedUrl", () => {
it("should return true when url is valid", async () => {
const result = isFullyQualifiedUrl("https://diamond.ac.uk:4000/path1");
expect(result).toEqual(true);
});

it("should return false when url is undefined", async () => {
const result = isFullyQualifiedUrl(undefined);
expect(result).toEqual(false);
});

it("should return false when url is invalid", async () => {
const result = isFullyQualifiedUrl("abcde1234.ac.uk");
expect(result).toEqual(false);
});
});

describe("buildUrl", () => {
it("should use base url when the joined path args don't make a fully qualified URL", async () => {
const baseUrl = "http://diamond.ac.uk";
const args = ["path1"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path1");
});

it("should return the default base url when the path args not specified", async () => {
const baseUrl = "http://diamond.ac.uk";

const result = buildUrl(baseUrl);

expect(result).toEqual("http://diamond.ac.uk/");
});

it("should return the default base url when the only path arg is undefined", async () => {
const baseUrl = "http://diamond.ac.uk";

const result = buildUrl(baseUrl, undefined);

expect(result).toEqual("http://diamond.ac.uk/");
});

it("url encodes the path string", async () => {
const baseUrl = "http://diamond.ac.uk";
const args = ["path 1"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path%201");
});

it("removes trailing and leading slash", async () => {
const baseUrl = "http://diamond.ac.uk/";
const args = ["/path1/"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path1");
});

it("Joins multiple path args with / separator", async () => {
const baseUrl = "http://diamond.ac.uk";
const args = ["/path1/", "/path2", "path3/"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path1/path2/path3");
});

it("Joins multiple path args with / separator, ignores filename component of host path", async () => {
const baseUrl = "http://diamond.ac.uk/path0/filename";
const args = ["/path1/"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path0/path1");
});

it("Joins multiple path args with / separator, trailing slash on the url", async () => {
const baseUrl = "http://diamond.ac.uk/path0/path1/";
const args = ["/path10/", "/path20"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://diamond.ac.uk/path0/path1/path10/path20");
});

it("ignores default base url if the args start with a fully qualified url", async () => {
const baseUrl = "http://diamond.ac.uk";
const args = ["http://ral.ac.uk/", "path1", "path2"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("http://ral.ac.uk/path1/path2");
});

it("ignores default base url if the args start with a fully qualified url and even if the base Url contains a path", async () => {
const baseUrl = "https://diamond.ac.uk/path0";
const args = ["https://ral.ac.uk:4000/", "path1", "path2"];

const result = buildUrl(baseUrl, ...args);

expect(result).toEqual("https://ral.ac.uk:4000/path1/path2");
});

it("ignores default base url if the args start with a fully qualified url, when base url is invalid", async () => {
const args = ["http://ral.ac.uk/", "path1", "path2"];

const result = buildUrl("abcd", ...args);

expect(result).toEqual("http://ral.ac.uk/path1/path2");
});

it("ignores default base url if the args start with a fully qualified url, when base url is undefined", async () => {
const args = ["http://ral.ac.uk/", "path1", "path2"];

const result = buildUrl(undefined, ...args);

expect(result).toEqual("http://ral.ac.uk/path1/path2");
});

it("Returns a relative path if base url is an empty string and path is not a fully qualified URL", async () => {
const args = ["path1", "filename.json"];

const result = buildUrl("", ...args);

expect(result).toEqual("/path1/filename.json");
});
});
});
32 changes: 32 additions & 0 deletions src/utils/urlUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const isFullyQualifiedUrl = (url: string): boolean => {
try {
const parsed = new URL(url);
return parsed.protocol === "https:" || parsed.protocol === "http:";
} catch {
return false;
}
};

export const buildUrl = (
defaultBaseHost: string,
...args: (string | undefined)[]
) => {
const path =
args
?.filter(s => s != null && s !== "")
.map(s => s?.replace(/\/+$/, "").replace(/^\/+/, ""))
.join("/") ?? "";

if (isFullyQualifiedUrl(path)) {
const parsedUrl = new URL(path);
return parsedUrl.toString();
}

if (isFullyQualifiedUrl(defaultBaseHost)) {
const parsedUrl = new URL(path, defaultBaseHost);
return parsedUrl.toString();
}

// Assume a local relative path
return `/${path}`;
};
Loading