mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
fix(services): keep non-Spring-Boot run configurations in the Services tree (#443)
Services listed run configurations only while their session was alive (or when their whole type was added), so after a restart only Spring Boot configurations were back. Configurations that ran are now remembered per project and listed under their type, dimmed until they run again; ids of deleted configurations are ignored. Fixes #388 Co-authored-by: a-rookie-of-C-language <186315048+a-rookie-of-C-language@users.noreply.github.com>
This commit is contained in:
co-authored by
a-rookie-of-C-language
parent
b983e3d0b2
commit
d277fc1522
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { DEFAULT_TYPES, readTypes, runServiceItems, sessionDetail, sessionStatus } from "./services";
|
||||
import { DEFAULT_TYPES, readRan, readTypes, runServiceItems, sessionDetail, sessionStatus } from "./services";
|
||||
import { startedPort } from "./serverPort";
|
||||
import type { RunSession } from "./store";
|
||||
|
||||
@@ -106,6 +106,38 @@ describe("types added to Services", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("configurations that ran", () => {
|
||||
const dev = config("dev", "npm");
|
||||
const scratch = config("context:file", "python");
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
it("persists the ran configuration ids per project", () => {
|
||||
expect(readRan("/p")).toEqual([]);
|
||||
localStorage.setItem("openidea.services.ran:/p", JSON.stringify(["dev"]));
|
||||
expect(readRan("/p")).toEqual(["dev"]);
|
||||
expect(readRan("/q")).toEqual([]);
|
||||
});
|
||||
|
||||
it("lists previously run configurations without a session", () => {
|
||||
const items = runServiceItems([dev, scratch], [], [], ["dev", "context:file"]);
|
||||
expect(items.map((i) => [i.key, i.label])).toEqual([
|
||||
["run:type:npm", "npm"],
|
||||
["run:type:python", "Python"],
|
||||
]);
|
||||
expect(items[0]!.children!.map((c) => [c.label, c.dim])).toEqual([["dev", true]]);
|
||||
expect(items[1]!.children!.map((c) => c.key)).toEqual(["run:config:context:file"]);
|
||||
});
|
||||
|
||||
it("ignores ran ids whose configuration is gone and merges sessions", () => {
|
||||
const gone = runServiceItems([dev], [], [], ["missing"]);
|
||||
expect(gone).toEqual([]);
|
||||
const running = session("s1", dev, "running", 1);
|
||||
const items = runServiceItems([dev], [running], [], ["dev"]);
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0]!.children!.map((c) => [c.label, c.detail])).toEqual([["dev", "Running"]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("embedded web server port", () => {
|
||||
it("reads the port from the started line", () => {
|
||||
expect(startedPort("TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'")).toBe(8080);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Run configurations in the Services tool window (IDEA's Run Dashboard): Spring
|
||||
* Boot by default, the configuration types added there ("Add Service › Run
|
||||
* Configuration Type") and the types of this window's run sessions, one group
|
||||
* Configuration Type"), the types of this window's run sessions and the
|
||||
* configurations that ran before (kept per project across restarts), one group
|
||||
* each, with run / debug / stop / rerun per configuration. A configuration that
|
||||
* ran shows its console in the detail pane, a running web application its HTTP port.
|
||||
*/
|
||||
@@ -30,6 +31,7 @@ export const RUN_PROVIDER = "runConfigurations";
|
||||
const TYPE_PREFIX = "run:type:";
|
||||
const CONFIG_PREFIX = "run:config:";
|
||||
const STORAGE_KEY = "openidea.services.runTypes:";
|
||||
const RAN_KEY = "openidea.services.ran:";
|
||||
/** Types shown in Services until the user changes the list. */
|
||||
export const DEFAULT_TYPES: readonly string[] = ["spring-boot"];
|
||||
|
||||
@@ -77,6 +79,41 @@ export function removeType(kind: string) {
|
||||
setAddedTypes(currentTypes().filter((t) => t !== kind));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configurations that ran, per project (shown again after a restart)
|
||||
|
||||
const useRanConfigs = create<{ root: string | null; ids: string[] }>(() => ({ root: null, ids: [] }));
|
||||
|
||||
/** The project's previously run configuration ids, most recent last. */
|
||||
export function readRan(root: string): string[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(RAN_KEY + root);
|
||||
if (raw === null) return [];
|
||||
const v: unknown = JSON.parse(raw);
|
||||
return Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function ranIds(root: string | null): string[] {
|
||||
const s = useRanConfigs.getState();
|
||||
return s.root === root ? s.ids : root ? readRan(root) : [];
|
||||
}
|
||||
|
||||
/** Records a run, so its configuration is back in Services after a restart. */
|
||||
export function recordRun(configId: string) {
|
||||
const root = useProject.getState().project?.root ?? null;
|
||||
const ids = [...ranIds(root).filter((id) => id !== configId), configId];
|
||||
useRanConfigs.setState({ root, ids });
|
||||
if (!root) return;
|
||||
try {
|
||||
localStorage.setItem(RAN_KEY + root, JSON.stringify(ids));
|
||||
} catch {
|
||||
/* storage unavailable */
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree
|
||||
|
||||
@@ -113,12 +150,19 @@ export function runServiceItems(
|
||||
configs: readonly RunConfig[],
|
||||
sessions: readonly RunSession[],
|
||||
added: readonly string[],
|
||||
ran: readonly string[] = [],
|
||||
): ServiceTreeItem[] {
|
||||
const latest = latestSessions(sessions);
|
||||
const kinds = [...added];
|
||||
for (const s of sessions) if (!kinds.includes(s.config.kind)) kinds.push(s.config.kind);
|
||||
for (const id of ran) {
|
||||
const kind = configs.find((c) => c.id === id)?.kind;
|
||||
if (kind && !kinds.includes(kind)) kinds.push(kind);
|
||||
}
|
||||
return kinds.flatMap((kind) => {
|
||||
const entries: RunConfig[] = added.includes(kind) ? configs.filter((c) => c.kind === kind) : [];
|
||||
const entries: RunConfig[] = added.includes(kind)
|
||||
? configs.filter((c) => c.kind === kind)
|
||||
: ran.map((id) => configs.find((c) => c.id === id)).filter((c): c is RunConfig => !!c && c.kind === kind);
|
||||
for (const s of latest.values())
|
||||
if (s.config.kind === kind && !entries.some((c) => c.id === s.config.id)) entries.push(s.config);
|
||||
if (!entries.length && DEFAULT_TYPES.includes(kind)) return [];
|
||||
@@ -155,12 +199,15 @@ function useRoots(): ServiceTreeItem[] {
|
||||
const sessions = useRun((s) => s.sessions);
|
||||
const types = useAddedTypes((s) => s.types);
|
||||
const typesRoot = useAddedTypes((s) => s.root);
|
||||
const ran = useRanConfigs((s) => s.ids);
|
||||
const ranRoot = useRanConfigs((s) => s.root);
|
||||
useEffect(() => {
|
||||
if (typesRoot !== root) useAddedTypes.setState({ root, types: root ? readTypes(root) : [] });
|
||||
}, [root, typesRoot]);
|
||||
if (ranRoot !== root) useRanConfigs.setState({ root, ids: root ? readRan(root) : [] });
|
||||
}, [root, typesRoot, ranRoot]);
|
||||
return useMemo(
|
||||
() => runServiceItems(configs, sessions, typesRoot === root ? types : []),
|
||||
[configs, sessions, types, typesRoot, root],
|
||||
() => runServiceItems(configs, sessions, typesRoot === root ? types : [], ranRoot === root ? ran : []),
|
||||
[configs, sessions, types, typesRoot, ran, ranRoot, root],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import { compoundChildren } from "./configs";
|
||||
import { remoteLabel, startWithRemedies } from "./remote";
|
||||
import { TerminalOutput } from "./terminalOutput";
|
||||
import { typeFor } from "./types";
|
||||
import { recordRun } from "./services";
|
||||
import { startedPort } from "./serverPort";
|
||||
import { tomcatBrowserUrl } from "./tomcat";
|
||||
import { openUrl } from "./navigate";
|
||||
@@ -571,6 +572,8 @@ export async function startRun(
|
||||
useRun.setState((s) => ({ sessions: [...s.sessions, created], activeSession: created.id }));
|
||||
}
|
||||
bump(session.id);
|
||||
// The Services tool window lists the configuration again after a restart.
|
||||
recordRun(config.id);
|
||||
const sessionId = session.id;
|
||||
const rt = runtimes.get(sessionId)!;
|
||||
const generation = rt.generation;
|
||||
|
||||
Reference in New Issue
Block a user