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

Feature/metadata access #89

Merged
merged 16 commits into from
Jul 18, 2024
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "foxbox",
"version": "1.0.11",
"version": "1.1.0",
"license": "MPL-2.0",
"private": true,
"productName": "Foxbox",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
// found at http://www.apache.org/licenses/LICENSE-2.0
// You may not use this file except in compliance with the License.

import { ParameterValue } from "@foxglove/studio";
import { Metadata, ParameterValue } from "@foxglove/studio";
import { freezeMetadata } from "@foxglove/studio-base/players/IterablePlayer/freezeMetadata";
import {
PlayerCapabilities,
PlayerStateActiveData,
Expand Down Expand Up @@ -100,4 +101,16 @@ export default class FakePlayer implements Player {
public setGlobalVariables = (): void => {
// no-op
};
public getMetadata = (): readonly Metadata[] => {
const metadata = [
{
name: "metadataFake",
metadata: { key: "value" },
},
];

freezeMetadata(metadata);

return metadata;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import { createStore } from "zustand";

import { Condvar } from "@foxglove/den/async";
import { Time, isLessThan } from "@foxglove/rostime";
import { ParameterValue } from "@foxglove/studio";
import { Metadata, ParameterValue } from "@foxglove/studio";
import {
FramePromise,
pauseFrameForPromises,
} from "@foxglove/studio-base/components/MessagePipeline/pauseFrameForPromise";
import { BuiltinPanelExtensionContext } from "@foxglove/studio-base/components/PanelExtensionAdapter";
import { freezeMetadata } from "@foxglove/studio-base/players/IterablePlayer/freezeMetadata";
import {
AdvertiseOptions,
MessageEvent,
Expand Down Expand Up @@ -176,7 +177,16 @@ function getPublicState(
setPlaybackSpeed:
props.capabilities?.includes(PlayerCapabilities.setSpeed) === true ? noop : undefined,
seekPlayback: props.seekPlayback,

getMetadata: () => {
const mockMetadata: ReadonlyArray<Readonly<Metadata>> = [
{
name: "mockMetadata",
metadata: { key: "value" },
},
];
freezeMetadata(mockMetadata);
return mockMetadata;
},
pauseFrame:
props.pauseFrame ??
function (name) {
Expand Down
22 changes: 22 additions & 0 deletions packages/studio-base/src/components/MessagePipeline/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
sortedTopics: [],
datatypes: new Map(),
setSubscriptions: expect.any(Function),
getMetadata: expect.any(Function),
setPublishers: expect.any(Function),
publish: expect.any(Function),
callService: expect.any(Function),
fetchAsset: expect.any(Function),
startPlayback: undefined,
playUntil: undefined,
pausePlayback: undefined,
setPlaybackSpeed: undefined,
seekPlayback: undefined,
Expand All @@ -101,6 +103,7 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
activeData: undefined,
capabilities: [],
presence: PlayerPresence.NOT_PRESENT,
profile: undefined,
playerId: "",
progress: {},
},
Expand All @@ -110,11 +113,13 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
datatypes: new Map(),
setSubscriptions: expect.any(Function),
setPublishers: expect.any(Function),
getMetadata: expect.any(Function),
publish: expect.any(Function),
callService: expect.any(Function),
fetchAsset: expect.any(Function),
startPlayback: undefined,
pausePlayback: undefined,
playUntil: undefined,
setPlaybackSpeed: undefined,
seekPlayback: undefined,
setParameter: expect.any(Function),
Expand Down Expand Up @@ -948,4 +953,21 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
expect(secondPlayerHasFinishedFrame).toEqual(true);
});
});

describe("metadata", () => {
it("should return the correct metadata", () => {
const player = new FakePlayer();
const { Hook, Wrapper } = makeTestHook({ player });
const { result } = renderHook(Hook, {
wrapper: Wrapper,
});

expect(result.current.getMetadata()).toEqual([
{
name: "metadataFake",
metadata: { key: "value" },
},
]);
});
});
});
4 changes: 4 additions & 0 deletions packages/studio-base/src/components/MessagePipeline/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ export function createMessagePipelineStore({
// Use a regular fetch for all other protocols
return await builtinFetch(uri, options);
},
getMetadata() {
aneuwald-ctw marked this conversation as resolved.
Show resolved Hide resolved
const player = get().player;
return player?.getMetadata?.() ?? Object.freeze([]);
},
startPlayback: undefined,
playUntil: undefined,
pausePlayback: undefined,
Expand Down
3 changes: 2 additions & 1 deletion packages/studio-base/src/components/MessagePipeline/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { Time } from "@foxglove/rostime";
import { Immutable, MessageEvent, ParameterValue } from "@foxglove/studio";
import { Immutable, MessageEvent, Metadata, ParameterValue } from "@foxglove/studio";
import { BuiltinPanelExtensionContext } from "@foxglove/studio-base/components/PanelExtensionAdapter";
import {
AdvertiseOptions,
Expand All @@ -25,6 +25,7 @@ export type MessagePipelineContext = Immutable<{
setPublishers: (id: string, publishersForId: AdvertiseOptions[]) => void;
setParameter: (key: string, value: ParameterValue) => void;
publish: (request: PublishPayload) => void;
getMetadata: () => ReadonlyArray<Readonly<Metadata>>;
callService: (service: string, request: unknown) => Promise<unknown>;
fetchAsset: BuiltinPanelExtensionContext["unstable_fetchAsset"];
startPlayback?: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,4 +722,47 @@ describe("PanelExtensionAdapter", () => {
[expect.any(String), []],
]);
});

it("should read metadata correctly", async () => {
expect.assertions(2);

const config = {};
const saveConfig = () => {};

const sig = signal();

const initPanel = (context: PanelExtensionContext) => {
expect(context.metadata).toBeDefined();
expect(context.metadata).toEqual([
{
name: "mockMetadata",
metadata: { key: "value" },
},
]);
sig.resolve();
};

const Wrapper = () => {
return (
<ThemeProvider isDark>
<MockPanelContextProvider>
<PanelSetup>
<PanelExtensionAdapter
config={config}
saveConfig={saveConfig}
initPanel={initPanel}
/>
</PanelSetup>
</MockPanelContextProvider>
</ThemeProvider>
);
};

await act(async () => undefined);
const handle = render(<Wrapper />);

// force a re-render to make sure we call init panel once
handle.rerender(<Wrapper />);
await sig;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function PanelExtensionAdapter(

const messagePipelineContext = useMessagePipeline(selectContext);

const { playerState, pauseFrame, setSubscriptions, seekPlayback, sortedTopics } =
const { playerState, pauseFrame, setSubscriptions, seekPlayback, getMetadata, sortedTopics } =
messagePipelineContext;

const { capabilities, profile: dataSourceProfile, presence: playerPresence } = playerState;
Expand Down Expand Up @@ -290,6 +290,7 @@ function PanelExtensionAdapter(
const updatePanelSettingsTree = usePanelSettingsTreeUpdate();

type PartialPanelExtensionContext = Omit<BuiltinPanelExtensionContext, "panelElement">;

const partialExtensionContext = useMemo<PartialPanelExtensionContext>(() => {
const layout: PanelExtensionContext["layout"] = {
addPanel({ position, type, updateIfExists, getState }) {
Expand Down Expand Up @@ -322,6 +323,8 @@ function PanelExtensionAdapter(

layout,

metadata: getMetadata(),

seekPlayback: seekPlayback
? (stamp: number | Time) => {
if (!isMounted()) {
Expand Down Expand Up @@ -513,6 +516,8 @@ function PanelExtensionAdapter(
setMessagePathDropConfig(dropConfig);
},
};
// Disable this rule because the metadata function. If used, it will break.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
capabilities,
clearHoverValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { Time } from "@foxglove/rostime";
import { Immutable, MessageEvent } from "@foxglove/studio";
import { Immutable, MessageEvent, Metadata } from "@foxglove/studio";
import {
PlayerProblem,
Topic,
Expand All @@ -20,6 +20,7 @@ export type Initalization = {
datatypes: RosDatatypes;
profile: string | undefined;
name?: string;
metadata?: ReadonlyArray<Readonly<Metadata>>;

/** Publisher names by topic **/
publishersByTopic: Map<string, Set<string>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TestSource implements IIterableSource {
problems: [],
datatypes: new Map(),
publishersByTopic: new Map(),
metadata: [{ name: "metadata1", metadata: { key: "value" } }],
};
}

Expand Down Expand Up @@ -754,6 +755,7 @@ describe("IterablePlayer", () => {
player.close();
await player.isClosed;
});

it("should make a new message iterator when topic subscriptions change", async () => {
const source = new TestSource();
const player = new IterablePlayer({
Expand Down Expand Up @@ -838,4 +840,33 @@ describe("IterablePlayer", () => {
player.close();
await player.isClosed;
});

it("should return the correct frozen metadata", async () => {
const source = new TestSource();
const player = new IterablePlayer({
source,
enablePreload: false,
sourceId: "test",
});

const metadata = player.getMetadata();

// At first, metadata is empty because it's initialized in an async way.
expect(metadata.length).toBe(0);

// Setup store to player update to be in start-play state
const store = new PlayerStateStore(4);
player.setListener(async (state) => {
await store.add(state);
});
// Wait for player to be in start-play state
await store.done;

const metadataInitialized = player.getMetadata();
expect(metadataInitialized.length).toBe(1);
expect(() => {
// @ts-expect-error because the array is type as readonly
metadataInitialized.pop();
}).toThrow();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
toRFC3339String,
toString,
} from "@foxglove/rostime";
import { Immutable, MessageEvent, ParameterValue } from "@foxglove/studio";
import { Immutable, MessageEvent, Metadata, ParameterValue } from "@foxglove/studio";
import { freezeMetadata } from "@foxglove/studio-base/players/IterablePlayer/freezeMetadata";
import NoopMetricsCollector from "@foxglove/studio-base/players/NoopMetricsCollector";
import PlayerProblemManager from "@foxglove/studio-base/players/PlayerProblemManager";
import {
Expand Down Expand Up @@ -174,6 +175,8 @@ export class IterablePlayer implements Player {

readonly #sourceId: string;

#metadata: readonly Metadata[] = Object.freeze([]);

#untilTime?: Time;

/** Promise that resolves when the player is closed. Only used for testing currently */
Expand Down Expand Up @@ -365,6 +368,10 @@ export class IterablePlayer implements Player {
// no-op
}

public getMetadata(): ReadonlyArray<Readonly<Metadata>> {
return this.#metadata;
}

/** Request the state to switch to newState */
#setState(newState: IterablePlayerState) {
// nothing should override closing the player
Expand Down Expand Up @@ -465,6 +472,7 @@ export class IterablePlayer implements Player {
profile,
topicStats,
problems,
metadata,
publishersByTopic,
datatypes,
name,
Expand All @@ -476,6 +484,13 @@ export class IterablePlayer implements Player {
this.#seekTarget = clampTime(this.#seekTarget, start, end);
}

this.#metadata = metadata ?? [];

// This freezing has to be done here, in the main thread.
// If it is done inside the web worker, it will be serialized and deserialized
// and will lose the frozen attributes.
freezeMetadata(this.#metadata);

this.#profile = profile;
this.#start = start;
this.#currentTime = this.#seekTarget ?? start;
Expand Down
Loading
Loading