diff --git a/apps/parsley/src/components/LogRow/BaseRow/SharingMenu/utils.test.ts b/apps/parsley/src/components/LogRow/BaseRow/SharingMenu/utils.test.ts
index 97dd34759..a5ffcdf57 100644
--- a/apps/parsley/src/components/LogRow/BaseRow/SharingMenu/utils.test.ts
+++ b/apps/parsley/src/components/LogRow/BaseRow/SharingMenu/utils.test.ts
@@ -26,14 +26,16 @@ describe("getLinesInProcessedLogLinesFromSelectedLines", () => {
2,
{ range: { end: 4, start: 3 }, rowType: RowType.SkippedLines },
{
+ functionID: "function-4",
functionName: "test",
isOpen: true,
range: { end: 6, start: 4 },
rowType: RowType.SectionHeader,
},
{
+ commandID: "command-4",
commandName: "shell.exec",
- functionName: "test",
+ functionID: "function-4",
isOpen: true,
range: { end: 6, start: 4 },
rowType: RowType.SubsectionHeader,
diff --git a/apps/parsley/src/components/LogRow/RowRenderer/index.tsx b/apps/parsley/src/components/LogRow/RowRenderer/index.tsx
index a0cde0e9c..490028b7f 100644
--- a/apps/parsley/src/components/LogRow/RowRenderer/index.tsx
+++ b/apps/parsley/src/components/LogRow/RowRenderer/index.tsx
@@ -31,7 +31,7 @@ const ParsleyRow: RowRendererFunction = ({ processedLogLines }) => {
searchState,
sectioning,
} = useLogContext();
- const { openSection } = sectioning;
+ const { toggleFunctionSection } = sectioning;
const { prettyPrint, wordWrapFormat, wrap } = preferences;
const { searchTerm } = searchState;
@@ -76,9 +76,10 @@ const ParsleyRow: RowRendererFunction = ({ processedLogLines }) => {
if (isSectionHeaderRow(processedLogLine)) {
return (
diff --git a/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.stories.tsx b/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.stories.tsx
index d39d327ad..adab0149c 100644
--- a/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.stories.tsx
+++ b/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.stories.tsx
@@ -40,7 +40,8 @@ const Container = styled.div`
`;
const sectionHeaderProps = {
- onOpen: () => {},
+ functionID: "function-4",
+ onToggle: () => {},
open: true,
status: SectionStatus.Pass,
};
diff --git a/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.test.tsx b/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.test.tsx
index 31e61d8a5..393cd8f64 100644
--- a/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.test.tsx
+++ b/apps/parsley/src/components/LogRow/SectionHeader/SectionHeader.test.tsx
@@ -42,12 +42,15 @@ describe("SectionHeader", () => {
it("should call onOpen function when 'open' button is clicked", async () => {
const user = userEvent.setup();
- const onOpen = vi.fn();
- render();
+ const onToggle = vi.fn();
+ render();
const openButton = screen.getByDataCy("caret-toggle");
await user.click(openButton);
- expect(onOpen).toHaveBeenCalledTimes(1);
- expect(onOpen).toHaveBeenCalledWith("load_data", true);
+ expect(onToggle).toHaveBeenCalledTimes(1);
+ expect(onToggle).toHaveBeenCalledWith({
+ functionID: "function-4",
+ isOpen: true,
+ });
});
it("open and close state is controlled by the 'open' prop", async () => {
@@ -72,9 +75,10 @@ describe("SectionHeader", () => {
});
const sectionHeaderProps = {
+ functionID: "function-4",
functionName: "load_data",
lineIndex: 0,
- onOpen: vi.fn(),
+ onToggle: vi.fn(),
open: false,
status: SectionStatus.Pass,
};
diff --git a/apps/parsley/src/components/LogRow/SectionHeader/index.tsx b/apps/parsley/src/components/LogRow/SectionHeader/index.tsx
index 48e0d2dc2..b4dbe69db 100644
--- a/apps/parsley/src/components/LogRow/SectionHeader/index.tsx
+++ b/apps/parsley/src/components/LogRow/SectionHeader/index.tsx
@@ -7,21 +7,23 @@ import { useLogWindowAnalytics } from "analytics";
import { Row } from "components/LogRow/types";
import { SectionStatus } from "constants/logs";
import { size } from "constants/tokens";
-import { OpenSection } from "hooks/useSections";
+import { ToggleFunctionSection } from "hooks/useSections";
import { CaretToggle } from "../CaretToggle";
const { gray } = palette;
interface SectionHeaderProps extends Row {
functionName: string;
- onOpen: OpenSection;
+ functionID: string;
+ onToggle: ToggleFunctionSection;
open: boolean;
status: SectionStatus;
}
const SectionHeader: React.FC = ({
+ functionID,
functionName,
- onOpen,
+ onToggle,
open,
status,
}) => {
@@ -39,7 +41,7 @@ const SectionHeader: React.FC = ({
sectionName: functionName,
sectionType: "function",
});
- onOpen(functionName, !open);
+ onToggle({ functionID, isOpen: !open });
}}
open={open}
/>
diff --git a/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.stories.tsx b/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.stories.tsx
index caaafea61..62130dcfa 100644
--- a/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.stories.tsx
+++ b/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.stories.tsx
@@ -15,7 +15,7 @@ const SubsectionHeaderStory = () => {
{...SubsectionHeaderProps}
commandName="shell.exec"
lineIndex={0}
- onOpen={({ isOpen }) => setOpen(isOpen)}
+ onToggle={({ isOpen }) => setOpen(isOpen)}
open={open}
/>
diff --git a/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.test.tsx b/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.test.tsx
index ad49ec5dd..13fdbc787 100644
--- a/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.test.tsx
+++ b/apps/parsley/src/components/LogRow/SubsectionHeader/SubsectionHeader.test.tsx
@@ -25,12 +25,12 @@ describe("SubsectionHeader", () => {
it("should call onOpen function when 'open' button is clicked", async () => {
const user = userEvent.setup();
- const onOpen = vi.fn();
- render();
+ const onToggle = vi.fn();
+ render();
const openButton = screen.getByDataCy("caret-toggle");
await user.click(openButton);
- expect(onOpen).toHaveBeenCalledTimes(1);
- expect(onOpen).toHaveBeenCalledWith({
+ expect(onToggle).toHaveBeenCalledTimes(1);
+ expect(onToggle).toHaveBeenCalledWith({
commandID: "command-1",
functionID: "function-1",
isOpen: true,
@@ -63,6 +63,6 @@ const sectionHeaderProps = {
commandName: "shell.exec",
functionID: "function-1",
lineIndex: 0,
- onOpen: vi.fn(),
+ onToggle: vi.fn(),
open: false,
};
diff --git a/apps/parsley/src/components/LogRow/SubsectionHeader/index.tsx b/apps/parsley/src/components/LogRow/SubsectionHeader/index.tsx
index f1138b5a4..7fbfc58ac 100644
--- a/apps/parsley/src/components/LogRow/SubsectionHeader/index.tsx
+++ b/apps/parsley/src/components/LogRow/SubsectionHeader/index.tsx
@@ -4,6 +4,7 @@ import { Body } from "@leafygreen-ui/typography";
import { useLogWindowAnalytics } from "analytics";
import { Row } from "components/LogRow/types";
import { size } from "constants/tokens";
+import { ToggleCommandSection } from "hooks/useSections";
import { CaretToggle } from "../CaretToggle";
const { gray } = palette;
@@ -12,11 +13,7 @@ interface SectionHeaderProps extends Row {
commandName: string;
functionID: string;
commandID: string;
- onOpen: (v: {
- commandID: string;
- functionID: string;
- isOpen: boolean;
- }) => void;
+ onToggle: ToggleCommandSection;
open: boolean;
}
@@ -24,7 +21,7 @@ const SubsectionHeader: React.FC = ({
commandID,
commandName,
functionID,
- onOpen,
+ onToggle,
open,
}) => {
const { sendEvent } = useLogWindowAnalytics();
@@ -39,7 +36,7 @@ const SubsectionHeader: React.FC = ({
sectionName: commandName,
sectionType: "command",
});
- onOpen({ commandID, functionID, isOpen: !open });
+ onToggle({ commandID, functionID, isOpen: !open });
}}
open={open}
/>
diff --git a/apps/parsley/src/hooks/useSections/index.ts b/apps/parsley/src/hooks/useSections/index.ts
index 2823a9158..d2ce7f74a 100644
--- a/apps/parsley/src/hooks/useSections/index.ts
+++ b/apps/parsley/src/hooks/useSections/index.ts
@@ -4,14 +4,30 @@ import { useToastContext } from "context/toast";
import { useParsleySettings } from "hooks/useParsleySettings";
import { reportError } from "utils/errorReporting";
import { releaseSectioning } from "utils/featureFlag";
-import { SectionEntry, parseSections } from "./utils";
+import { SectionData, parseSections } from "./utils";
-export type SectionState = { [functionName: string]: { isOpen: boolean } };
-export type OpenSection = (functionName: string, isOpen: boolean) => void;
+export type SectionState = {
+ [functionID: string]: {
+ isOpen: boolean;
+ commands: { [commandID: string]: { isOpen: boolean } };
+ };
+};
+
+export type ToggleCommandSection = (props: {
+ functionID: string;
+ commandID: string;
+ isOpen: boolean;
+}) => void;
+
+export type ToggleFunctionSection = (props: {
+ functionID: string;
+ isOpen: boolean;
+}) => void;
export interface UseSectionsResult {
- sectionData: SectionEntry[] | undefined;
- openSection: OpenSection;
+ sectionData: SectionData | undefined;
+ toggleCommandSection: ToggleCommandSection;
+ toggleFunctionSection: ToggleFunctionSection;
sectionState: SectionState | undefined;
sectioningEnabled: boolean;
}
@@ -27,7 +43,7 @@ export const useSections = ({
renderingType,
}: Props): UseSectionsResult => {
const dispatchToast = useToastContext();
- const [sectionData, setSectionData] = useState();
+ const [sectionData, setSectionData] = useState();
const [sectionState, setSectionState] = useState();
const { settings } = useParsleySettings();
@@ -56,30 +72,53 @@ export const useSections = ({
useEffect(() => {
if (sectionData && sectionState === undefined) {
- setSectionState(sectionData.reduce(closeAllSectionsReducer, {}));
+ setSectionState(populateSectionState(sectionData));
}
}, [sectionData, sectionState]);
- const openSection = useCallback(
- (functionName: string, isOpen: boolean) => {
- if (sectionState) {
- setSectionState((currentState) => ({
- ...currentState,
- [functionName]: { isOpen },
- }));
- }
+ const toggleFunctionSection: ToggleFunctionSection = useCallback(
+ ({ functionID, isOpen }) => {
+ setSectionState((currentState) => {
+ if (currentState) {
+ const nextState = { ...currentState };
+ nextState[functionID].isOpen = isOpen;
+ return nextState;
+ }
+ return currentState;
+ });
+ },
+ [sectionState],
+ );
+ const toggleCommandSection: ToggleCommandSection = useCallback(
+ ({ commandID, functionID, isOpen }) => {
+ setSectionState((currentState) => {
+ if (currentState) {
+ const nextState = { ...currentState };
+ nextState[functionID].commands[commandID].isOpen = isOpen;
+ return nextState;
+ }
+ return currentState;
+ });
},
[sectionState],
);
return {
- openSection,
sectionData,
sectionState,
sectioningEnabled,
+ toggleCommandSection,
+ toggleFunctionSection,
};
};
-const closeAllSectionsReducer = (
- accum: SectionState,
- { functionName }: SectionEntry,
-) => ({ ...accum, ...{ [functionName]: { isOpen: false } } });
+const populateSectionState = (sectionData: SectionData): SectionState => {
+ const { commands, functions } = sectionData;
+ const sectionState: SectionState = {};
+ functions.forEach(({ functionID }) => {
+ sectionState[functionID] = { commands: {}, isOpen: false };
+ });
+ commands.forEach(({ commandID, functionID }) => {
+ sectionState[functionID].commands[commandID] = { isOpen: false };
+ });
+ return sectionState;
+};
diff --git a/apps/parsley/src/hooks/useSections/useSections.test.tsx b/apps/parsley/src/hooks/useSections/useSections.test.tsx
index ff5d8a987..fde67885a 100644
--- a/apps/parsley/src/hooks/useSections/useSections.test.tsx
+++ b/apps/parsley/src/hooks/useSections/useSections.test.tsx
@@ -33,7 +33,10 @@ describe("useSections", () => {
expect(sectionUtils.parseSections).toHaveBeenCalledOnce();
});
await waitFor(() => {
- expect(result.current.sectionData).toStrictEqual([]);
+ expect(result.current.sectionData).toStrictEqual({
+ commands: [],
+ functions: [],
+ });
});
});
@@ -85,9 +88,29 @@ describe("useSections", () => {
expect(sectionUtils.parseSections).toHaveBeenCalledOnce();
});
await waitFor(() => {
- expect(result.current.sectionData).toStrictEqual([
- { functionName: "f-1", range: { end: 3, start: 1 } },
- ]);
+ expect(result.current.sectionData).toStrictEqual({
+ commands: [
+ {
+ commandID: "command-1",
+ commandName: "c1",
+ functionID: "function-1",
+ range: {
+ end: 3,
+ start: 1,
+ },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-1",
+ functionName: "f-1",
+ range: {
+ end: 3,
+ start: 1,
+ },
+ },
+ ],
+ });
});
});
@@ -143,7 +166,7 @@ describe("useSections", () => {
});
describe("opening and closing sections", () => {
- it("openSection function toggles the open state", async () => {
+ it("toggleFunctionSection function toggles the open state", async () => {
RenderFakeToastContext();
const { result } = renderHook(() => useSections({ logs, ...metadata }), {
wrapper,
@@ -155,52 +178,175 @@ describe("useSections", () => {
expect(result.current.sectionState).toStrictEqual(initialSectionState);
});
act(() => {
- result.current.openSection("f-1", true);
+ result.current.toggleFunctionSection({
+ functionID: "function-1",
+ isOpen: true,
+ });
});
await waitFor(() => {
expect(result.current.sectionState).toStrictEqual({
...initialSectionState,
- "f-1": { isOpen: true },
+ "function-1": { ...initialSectionState["function-1"], isOpen: true },
});
});
act(() => {
- result.current.openSection("f-2", true);
+ result.current.toggleFunctionSection({
+ functionID: "function-9",
+ isOpen: true,
+ });
});
await waitFor(() => {
expect(result.current.sectionState).toStrictEqual({
...initialSectionState,
- "f-1": { isOpen: true },
- "f-2": { isOpen: true },
+ "function-1": { ...initialSectionState["function-1"], isOpen: true },
+ "function-9": { ...initialSectionState["function-9"], isOpen: true },
});
});
act(() => {
- result.current.openSection("f-1", false);
+ result.current.toggleFunctionSection({
+ functionID: "function-1",
+ isOpen: false,
+ });
});
await waitFor(() => {
expect(result.current.sectionState).toStrictEqual({
...initialSectionState,
- "f-2": { isOpen: true },
+ "function-9": { ...initialSectionState["function-9"], isOpen: true },
+ });
+ });
+ });
+
+ it("toggleCommandSection toggles the open state", async () => {
+ RenderFakeToastContext();
+ const { result } = renderHook(() => useSections({ logs, ...metadata }), {
+ wrapper,
+ });
+ await waitFor(() => {
+ expect(result.current.sectionData).toStrictEqual(sectionData);
+ });
+ await waitFor(() => {
+ expect(result.current.sectionState).toStrictEqual(initialSectionState);
+ });
+ act(() => {
+ result.current.toggleCommandSection({
+ commandID: "command-9",
+ functionID: "function-9",
+ isOpen: true,
+ });
+ });
+ await waitFor(() => {
+ expect(result.current.sectionState).toStrictEqual({
+ ...initialSectionState,
+ "function-9": {
+ commands: {
+ ...initialSectionState["function-9"].commands,
+ "command-9": { isOpen: true },
+ },
+ isOpen: false,
+ },
});
});
});
const logs = [
"normal log line",
"Running command 'c1' in function 'f-1'.",
+ "normal log line",
+ "normal log line",
+ "normal log line",
"Finished command 'c1' in function 'f-1'.",
- "Running command 'c1' in function 'f-2'.",
- "Finished command 'c1' in function 'f-2'.",
- "Running command 'c1' in function 'f-3'.",
- "Finished command 'c1' in function 'f-3'.",
- ];
- const sectionData = [
- { functionName: "f-1", range: { end: 3, start: 1 } },
- { functionName: "f-2", range: { end: 5, start: 3 } },
- { functionName: "f-3", range: { end: 7, start: 5 } },
+ "Running command 'c2' in function 'f-1'.",
+ "Finished command 'c2' in function 'f-1'.",
+ "normal log line",
+ "Running command 'c3' in function 'f-2'.",
+ "normal log line",
+ "Finished command 'c3' in function 'f-2'.",
+ "Running command 'c4' in function 'f-2'.",
+ "Finished command 'c4' in function 'f-2'.",
+ "normal log line",
+ "normal log line",
+ "normal log line",
];
+ const sectionData: sectionUtils.SectionData = {
+ commands: [
+ {
+ commandID: "command-1",
+ commandName: "c1",
+ functionID: "function-1",
+ range: {
+ end: 6,
+ start: 1,
+ },
+ },
+ {
+ commandID: "command-6",
+ commandName: "c2",
+ functionID: "function-1",
+ range: {
+ end: 8,
+ start: 6,
+ },
+ },
+ {
+ commandID: "command-9",
+ commandName: "c3",
+ functionID: "function-9",
+ range: {
+ end: 12,
+ start: 9,
+ },
+ },
+ {
+ commandID: "command-12",
+ commandName: "c4",
+ functionID: "function-9",
+ range: {
+ end: 14,
+ start: 12,
+ },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-1",
+ functionName: "f-1",
+ range: {
+ end: 8,
+ start: 1,
+ },
+ },
+ {
+ functionID: "function-9",
+ functionName: "f-2",
+ range: {
+ end: 14,
+ start: 9,
+ },
+ },
+ ],
+ };
const initialSectionState = {
- "f-1": { isOpen: false },
- "f-2": { isOpen: false },
- "f-3": { isOpen: false },
+ "function-1": {
+ commands: {
+ "command-1": {
+ isOpen: false,
+ },
+ "command-6": {
+ isOpen: false,
+ },
+ },
+ isOpen: false,
+ },
+ "function-9": {
+ commands: {
+ "command-9": {
+ isOpen: false,
+ },
+ "command-12": {
+ isOpen: false,
+ },
+ },
+ isOpen: false,
+ },
};
});
const metadata = {
diff --git a/apps/parsley/src/hooks/useSections/utils.test.ts b/apps/parsley/src/hooks/useSections/utils.test.ts
index 4ad4f1af6..822880e6f 100644
--- a/apps/parsley/src/hooks/useSections/utils.test.ts
+++ b/apps/parsley/src/hooks/useSections/utils.test.ts
@@ -1,24 +1,26 @@
-import { SectionEntry, parseSections, processLine, reduceFn } from "./utils";
+import { SectionData, parseSections, processLine, reduceFn } from "./utils";
describe("processLine", () => {
it("should correctly parse a log line indicating a running section", () => {
const logLine =
"Running command 'ec2.assume_role' in function 'assume-ec2-role' (step 1 of 4) in block 'pre'.";
const expectedMetadata = {
+ commandName: "ec2.assume_role",
functionName: "assume-ec2-role",
status: "Running",
};
- expect(processLine(logLine)).toEqual(expectedMetadata);
+ expect(processLine(logLine)).toStrictEqual(expectedMetadata);
});
it("should correctly parse a log line indicating a finished section", () => {
const logLine =
"Finished command 'shell.exec' in function 'yarn-preview' (step 6 of 9) in 415.963µs.";
const expectedMetadata = {
+ commandName: "shell.exec",
functionName: "yarn-preview",
status: "Finished",
};
- expect(processLine(logLine)).toEqual(expectedMetadata);
+ expect(processLine(logLine)).toStrictEqual(expectedMetadata);
});
it("should return null for a log line that does not indicate a section", () => {
@@ -29,35 +31,75 @@ describe("processLine", () => {
describe("reduceFn", () => {
it("accumulate section data for starting a section", () => {
- const accum: SectionEntry[] = [];
+ const accum: SectionData = { commands: [], functions: [] };
const line = "Running command 'shell.exec' in function 'yarn-preview'.";
const logIndex = 0;
- const expectedSections = [
- {
- functionName: "yarn-preview",
- range: { end: -1, start: 0 },
- },
- ];
- expect(reduceFn(accum, line, logIndex)).toEqual(expectedSections);
+ expect(reduceFn(accum, line, logIndex)).toEqual({
+ commands: [
+ {
+ commandID: "command-0",
+ commandName: "shell.exec",
+ functionID: "function-0",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-0",
+ functionName: "yarn-preview",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ });
});
it("accumulate section data for finishing a section", () => {
- const accum: SectionEntry[] = [
- { functionName: "yarn-preview", range: { end: -1, start: 0 } },
- ];
+ const accum: SectionData = {
+ commands: [
+ {
+ commandID: "command-0",
+ commandName: "shell.exec",
+ functionID: "function-0",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-0",
+ functionName: "yarn-preview",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ };
const line =
"Finished command 'shell.exec' in function 'yarn-preview' (step 6 of 9) in 415.963µs.";
const logIndex = 4;
- const expectedSections = [
- {
- functionName: "yarn-preview",
- range: { end: 5, start: 0 },
- },
- ];
- expect(reduceFn(accum, line, logIndex)).toEqual(expectedSections);
+ expect(reduceFn(accum, line, logIndex)).toEqual({
+ commands: [
+ {
+ commandID: "command-0",
+ commandName: "shell.exec",
+ functionID: "function-0",
+ range: {
+ end: 5,
+ start: 0,
+ },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-0",
+ functionName: "yarn-preview",
+ range: {
+ end: 5,
+ start: 0,
+ },
+ },
+ ],
+ });
});
it("should throw an error if a finished section appears before a running section", () => {
- const accum: SectionEntry[] = [];
+ const accum: SectionData = { commands: [], functions: [] };
const line =
"Finished command 'shell.exec' in function 'yarn-preview' (step 6 of 9) in 415.963µs.";
const logIndex = 0;
@@ -69,9 +111,23 @@ describe("reduceFn", () => {
});
it("should throw an error if a new running section starts without finishing the previous section", () => {
- const accum = [
- { functionName: "yarn-preview", range: { end: -1, start: 0 } },
- ];
+ const accum = {
+ commands: [
+ {
+ commandID: "command-0",
+ commandName: "shell.exec",
+ functionID: "function-0",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-0",
+ functionName: "yarn-preview",
+ range: { end: -1, start: 0 },
+ },
+ ],
+ };
const line =
"Running command 'attach.xunit_results' in function 'attach-cypress-results' (step 3.3 of 8) in block 'post'.";
const logIndex = 1;
@@ -102,11 +158,81 @@ describe("parseSections", () => {
"normal log line",
"normal log line",
];
- const expectedSections = [
- { functionName: "f-1", range: { end: 5, start: 1 } },
- { functionName: "f-2", range: { end: 11, start: 6 } },
- { functionName: "f-3", range: { end: 15, start: 11 } },
- ];
+ const expectedSections = {
+ commands: [
+ {
+ commandID: "command-1",
+ commandName: "c1",
+ functionID: "function-1",
+ range: {
+ end: 3,
+ start: 1,
+ },
+ },
+ {
+ commandID: "command-3",
+ commandName: "c2",
+ functionID: "function-1",
+ range: {
+ end: 5,
+ start: 3,
+ },
+ },
+ {
+ commandID: "command-6",
+ commandName: "c3",
+ functionID: "function-6",
+ range: {
+ end: 9,
+ start: 6,
+ },
+ },
+ {
+ commandID: "command-9",
+ commandName: "c4",
+ functionID: "function-6",
+ range: {
+ end: 11,
+ start: 9,
+ },
+ },
+ {
+ commandID: "command-11",
+ commandName: "c5",
+ functionID: "function-11",
+ range: {
+ end: 15,
+ start: 11,
+ },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-1",
+ functionName: "f-1",
+ range: {
+ end: 5,
+ start: 1,
+ },
+ },
+ {
+ functionID: "function-6",
+ functionName: "f-2",
+ range: {
+ end: 11,
+ start: 6,
+ },
+ },
+ {
+ functionID: "function-11",
+ functionName: "f-3",
+ range: {
+ end: 15,
+ start: 11,
+ },
+ },
+ ],
+ };
expect(parseSections(logs)).toEqual(expectedSections);
});
@@ -114,6 +240,9 @@ describe("parseSections", () => {
const logs = [
"normal log line",
"Running command 'c1' in function 'f-1'.",
+ "normal log line",
+ "normal log line",
+ "normal log line",
"Finished command 'c1' in function 'f-1'.",
"Running command 'c2' in function 'f-1'.",
"Finished command 'c2' in function 'f-1'.",
@@ -127,11 +256,64 @@ describe("parseSections", () => {
"normal log line",
"normal log line",
];
- const expectedSections = [
- { functionName: "f-1", range: { end: 5, start: 1 } },
- { functionName: "f-2", range: { end: 11, start: 6 } },
- ];
- expect(parseSections(logs)).toEqual(expectedSections);
+ expect(parseSections(logs)).toEqual({
+ commands: [
+ {
+ commandID: "command-1",
+ commandName: "c1",
+ functionID: "function-1",
+ range: {
+ end: 6,
+ start: 1,
+ },
+ },
+ {
+ commandID: "command-6",
+ commandName: "c2",
+ functionID: "function-1",
+ range: {
+ end: 8,
+ start: 6,
+ },
+ },
+ {
+ commandID: "command-9",
+ commandName: "c3",
+ functionID: "function-9",
+ range: {
+ end: 12,
+ start: 9,
+ },
+ },
+ {
+ commandID: "command-12",
+ commandName: "c4",
+ functionID: "function-9",
+ range: {
+ end: 14,
+ start: 12,
+ },
+ },
+ ],
+ functions: [
+ {
+ functionID: "function-1",
+ functionName: "f-1",
+ range: {
+ end: 8,
+ start: 1,
+ },
+ },
+ {
+ functionID: "function-9",
+ functionName: "f-2",
+ range: {
+ end: 14,
+ start: 9,
+ },
+ },
+ ],
+ });
});
it("should return an error when there is a finished section without a running section before it", () => {
@@ -159,8 +341,8 @@ describe("parseSections", () => {
);
});
- it("should return an empty array if the logs array is empty", () => {
+ it("should return empty arrays if the logs array is empty", () => {
const logs: string[] = [];
- expect(parseSections(logs)).toEqual([]);
+ expect(parseSections(logs)).toEqual({ commands: [], functions: [] });
});
});
diff --git a/apps/parsley/src/hooks/useSections/utils.ts b/apps/parsley/src/hooks/useSections/utils.ts
index 6dd456ca0..40d7843f0 100644
--- a/apps/parsley/src/hooks/useSections/utils.ts
+++ b/apps/parsley/src/hooks/useSections/utils.ts
@@ -7,6 +7,7 @@ enum SectionStatus {
}
interface SectionLineMetadata {
+ commandName: string;
functionName: string;
status: SectionStatus;
}
@@ -17,20 +18,34 @@ interface SectionLineMetadata {
* or null if the input line does not indicate the start or end of a section.
*/
export const processLine = (str: string): SectionLineMetadata | null => {
- const regex = /(Running|Finished) command '[^']+' in function '([^']+)'.*/;
+ const regex = /(Running|Finished) command '([^']+)' in function '([^']+)'.*/;
const match = trimSeverity(str).match(regex);
if (match) {
return {
- functionName: match[2],
+ commandName: match[2],
+ functionName: match[3],
status: match[1] as SectionStatus,
};
}
return null;
};
-interface SectionEntry {
- range: Range;
+interface FunctionEntry {
+ functionID: string;
functionName: string;
+ range: Range;
+}
+
+interface CommandEntry {
+ commandID: string;
+ commandName: string;
+ functionID: string;
+ range: Range;
+}
+
+interface SectionData {
+ functions: FunctionEntry[];
+ commands: CommandEntry[];
}
/**
@@ -41,51 +56,69 @@ interface SectionEntry {
* @returns The updated accumulated section data after processing the current line.
*/
const reduceFn = (
- accum: SectionEntry[],
+ accum: SectionData,
line: string,
logIndex: number,
-): SectionEntry[] => {
+): SectionData => {
const currentLine = processLine(line);
// Skip if the current line does not indicate a section
if (!currentLine) {
return accum;
}
- const sections = [...accum];
+ const { commands, functions } = accum;
if (currentLine.status === SectionStatus.Finished) {
- if (sections.length === 0) {
+ if (functions.length === 0 || commands.length === 0) {
throw new Error(
"Log file is showing a finished section without a running section before it.",
);
}
// Update the end line number exclusive of the last section in the accumulator
- sections[sections.length - 1].range.end = logIndex + 1;
- return sections;
+ functions[functions.length - 1].range.end = logIndex + 1;
+ commands[commands.length - 1].range.end = logIndex + 1;
+ return { commands, functions };
}
- const isNewSection =
- sections.length === 0 ||
- sections[sections.length - 1].functionName !== currentLine.functionName;
/**
- * @description - ONGOING_SECTION is used to indicate that the section is still running and has not finished yet in the log file.
+ * @description - ONGOING_ENTRY is used to indicate that the section or command is still running and has not finished yet in the log file.
* The section parsing function will temporarily assign this value until the corresponding finished section or EOF is found.
*/
- const ONGOING_SECTION = -1;
+ const ONGOING_ENTRY = -1;
+ if (currentLine.status === SectionStatus.Running) {
+ const isNewSection =
+ functions.length === 0 ||
+ functions[functions.length - 1].functionName !== currentLine.functionName;
+ if (isNewSection) {
+ const isPreviousSectionRunning =
+ functions.length &&
+ functions[functions.length - 1].range.end === ONGOING_ENTRY;
- if (currentLine.status === SectionStatus.Running && isNewSection) {
- const isPreviousSectionRunning =
- sections.length &&
- sections[sections.length - 1].range.end === ONGOING_SECTION;
- if (isPreviousSectionRunning) {
+ if (isPreviousSectionRunning) {
+ throw new Error(
+ "Log file is showing a new running section without finishing the previous section.",
+ );
+ }
+ functions.push({
+ functionID: `function-${logIndex}`,
+ functionName: currentLine.functionName,
+ range: { end: ONGOING_ENTRY, start: logIndex },
+ });
+ }
+ const isPreviousCommandRunning =
+ commands.length &&
+ commands[commands.length - 1].range.end === ONGOING_ENTRY;
+ if (isPreviousCommandRunning) {
throw new Error(
- "Log file is showing a new running section without finishing the previous section.",
+ "Log file is showing a new running command without finishing the previous command.",
);
}
- sections.push({
- functionName: currentLine.functionName,
- range: { end: ONGOING_SECTION, start: logIndex },
+ commands.push({
+ commandID: `command-${logIndex}`,
+ commandName: currentLine.commandName,
+ functionID: functions[functions.length - 1].functionID,
+ range: { end: ONGOING_ENTRY, start: logIndex },
});
}
- return sections;
+ return { commands, functions };
};
/**
@@ -93,16 +126,24 @@ const reduceFn = (
* @param logs - The array of log lines to be parsed.
* @returns An array of section entries representing the parsed sections.
*/
-const parseSections = (logs: string[]): SectionEntry[] => {
- const result = logs.reduce(reduceFn, [] as SectionEntry[]);
+const parseSections = (logs: string[]): SectionData => {
+ const { commands, functions } = logs.reduce(reduceFn, {
+ commands: [],
+ functions: [],
+ } as SectionData);
const lastSectionIsRunning =
- result.length && result[result.length - 1].range.end === -1;
+ functions.length && functions[functions.length - 1].range.end === -1;
// Close the last section if it is still running
if (lastSectionIsRunning) {
- result[result.length - 1].range.end = logs.length;
+ functions[functions.length - 1].range.end = logs.length;
+ }
+ const lastCommandIsRunning =
+ commands.length && commands[commands.length - 1].range.end === -1;
+ if (lastCommandIsRunning) {
+ commands[commands.length - 1].range.end = logs.length;
}
- return result;
+ return { commands, functions };
};
export { parseSections, reduceFn };
-export type { SectionEntry };
+export type { FunctionEntry, SectionData, CommandEntry };
diff --git a/apps/parsley/src/types/logs.ts b/apps/parsley/src/types/logs.ts
index 8cb8012a0..2e6f9e549 100644
--- a/apps/parsley/src/types/logs.ts
+++ b/apps/parsley/src/types/logs.ts
@@ -21,18 +21,20 @@ interface SkippedLinesRow {
}
interface SectionHeaderRow {
- rowType: RowType.SectionHeader;
+ functionID: string;
functionName: string;
- range: Range;
isOpen: boolean;
+ range: Range;
+ rowType: RowType.SectionHeader;
}
interface SubsectionHeaderRow {
- rowType: RowType.SubsectionHeader;
- functionName: string;
+ commandID: string;
commandName: string;
- range: Range;
+ functionID: string;
isOpen: boolean;
+ range: Range;
+ rowType: RowType.SubsectionHeader;
}
type ProcessedLogLine =
diff --git a/apps/parsley/src/utils/filterLogs/filterLogs.test.ts b/apps/parsley/src/utils/filterLogs/filterLogs.test.ts
index 95300001c..7a82aa95a 100644
--- a/apps/parsley/src/utils/filterLogs/filterLogs.test.ts
+++ b/apps/parsley/src/utils/filterLogs/filterLogs.test.ts
@@ -159,7 +159,10 @@ describe("filterLogs", () => {
logLines: logsWithSections,
matchingLines: undefined,
sectionData,
- sectionState: { "f-1": { isOpen: true }, "f-2": { isOpen: true } },
+ sectionState: {
+ "function-1": { commands: {}, isOpen: true },
+ "function-6": { commands: {}, isOpen: true },
+ },
sectioningEnabled: true,
shareLine: undefined,
}),
@@ -176,7 +179,10 @@ describe("filterLogs", () => {
logLines: logsWithSections,
matchingLines: undefined,
sectionData,
- sectionState: { "f-1": { isOpen: false }, "f-2": { isOpen: true } },
+ sectionState: {
+ "function-1": { commands: {}, isOpen: false },
+ "function-6": { commands: {}, isOpen: true },
+ },
sectioningEnabled: true,
shareLine: undefined,
}),
@@ -193,7 +199,10 @@ describe("filterLogs", () => {
logLines: logsWithSections,
matchingLines: undefined,
sectionData,
- sectionState: { "f-1": { isOpen: false }, "f-2": { isOpen: false } },
+ sectionState: {
+ "f-1": { commands: {}, isOpen: false },
+ "f-2": { commands: {}, isOpen: false },
+ },
sectioningEnabled: true,
shareLine: undefined,
}),
@@ -255,14 +264,26 @@ const logsWithSections = [
"normal log line",
];
-const sectionData = [
- { functionName: "f-1", range: { end: 5, start: 1 } },
- { functionName: "f-2", range: { end: 11, start: 6 } },
-];
+const sectionData = {
+ commands: [],
+ functions: [
+ {
+ functionID: "function-1",
+ functionName: "f-1",
+ range: { end: 5, start: 1 },
+ },
+ {
+ functionID: "function-6",
+ functionName: "f-2",
+ range: { end: 11, start: 6 },
+ },
+ ],
+};
const allSectionsOpen = [
0,
{
+ functionID: "function-1",
functionName: "f-1",
isOpen: true,
range: {
@@ -277,6 +298,7 @@ const allSectionsOpen = [
4,
5,
{
+ functionID: "function-6",
functionName: "f-2",
isOpen: true,
range: {
@@ -297,6 +319,7 @@ const allSectionsOpen = [
const someSectionsOpen = [
0,
{
+ functionID: "function-1",
functionName: "f-1",
isOpen: false,
range: {
@@ -307,6 +330,7 @@ const someSectionsOpen = [
},
5,
{
+ functionID: "function-6",
functionName: "f-2",
isOpen: true,
range: {
@@ -328,6 +352,7 @@ const someSectionsOpen = [
const allSectionsClosed = [
0,
{
+ functionID: "function-1",
functionName: "f-1",
isOpen: false,
range: {
@@ -338,6 +363,7 @@ const allSectionsClosed = [
},
5,
{
+ functionID: "function-6",
functionName: "f-2",
isOpen: false,
range: {
diff --git a/apps/parsley/src/utils/filterLogs/index.ts b/apps/parsley/src/utils/filterLogs/index.ts
index f68df7f6e..332d2a2c4 100644
--- a/apps/parsley/src/utils/filterLogs/index.ts
+++ b/apps/parsley/src/utils/filterLogs/index.ts
@@ -1,5 +1,5 @@
import { SectionState } from "hooks/useSections";
-import { SectionEntry } from "hooks/useSections/utils";
+import { SectionData } from "hooks/useSections/utils";
import { ExpandedLines, ProcessedLogLines, RowType } from "types/logs";
import { isExpanded } from "utils/expandedLines";
import { newSkippedLinesRow } from "utils/logRow";
@@ -13,7 +13,7 @@ type FilterLogsParams = {
expandedLines: ExpandedLines;
expandableRows: boolean;
failingLine: number | undefined;
- sectionData: SectionEntry[] | undefined;
+ sectionData: SectionData | undefined;
sectioningEnabled: boolean;
sectionState: SectionState | undefined;
};
@@ -49,25 +49,24 @@ const filterLogs = (options: FilterLogsParams): ProcessedLogLines => {
} = options;
// If there are no filters or expandable rows is not enabled, then we only have to process sections if they exist and are enabled.
if (matchingLines === undefined) {
- if (sectioningEnabled && sectionData?.length) {
+ if (sectioningEnabled && sectionData?.functions.length) {
const filteredLines: ProcessedLogLines = [];
let sectionIndex = 0;
for (let idx = 0; idx < logLines.length; idx++) {
- const section = sectionData[sectionIndex];
- const isSectionStart = section && idx === section.range.start;
+ const func = sectionData.functions[sectionIndex];
+ const isSectionStart = func && idx === func.range.start;
if (isSectionStart && sectionState) {
- const isOpen = sectionState[section.functionName]?.isOpen ?? false;
+ const isOpen = sectionState[func.functionID]?.isOpen ?? false;
filteredLines.push({
- functionName: section.functionName,
+ ...func,
isOpen,
- range: section.range,
rowType: RowType.SectionHeader,
});
sectionIndex += 1;
if (isOpen) {
filteredLines.push(idx);
} else {
- idx = section.range.end - 1;
+ idx = func.range.end - 1;
}
} else {
filteredLines.push(idx);
diff --git a/apps/parsley/src/utils/findLineIndex/findLineIndex.test.ts b/apps/parsley/src/utils/findLineIndex/findLineIndex.test.ts
index 8a37d35f2..27f5352c7 100644
--- a/apps/parsley/src/utils/findLineIndex/findLineIndex.test.ts
+++ b/apps/parsley/src/utils/findLineIndex/findLineIndex.test.ts
@@ -9,39 +9,45 @@ const processedLines: ProcessedLogLines = [
6,
{ range: { end: 10, start: 7 }, rowType: RowType.SkippedLines },
{
+ functionID: "function-10",
functionName: "f-1",
isOpen: true,
range: { end: 11, start: 10 },
rowType: RowType.SectionHeader,
},
{
+ commandID: "command-10",
commandName: "shell.exec",
- functionName: "f-1",
+ functionID: "function-10",
isOpen: false,
range: { end: 11, start: 10 },
rowType: RowType.SubsectionHeader,
},
12,
{
+ functionID: "function-13",
functionName: "f-2",
isOpen: false,
range: { end: 15, start: 13 },
rowType: RowType.SectionHeader,
},
{
+ functionID: "function-15",
functionName: "f-3",
isOpen: true,
range: { end: 17, start: 15 },
rowType: RowType.SectionHeader,
},
{
+ commandID: "command-15",
commandName: "shell.exec",
- functionName: "f-3",
+ functionID: "function-15",
isOpen: false,
range: { end: 17, start: 15 },
rowType: RowType.SubsectionHeader,
},
{
+ functionID: "function-17",
functionName: "f-4",
isOpen: true,
range: { end: 19, start: 17 },
@@ -50,14 +56,16 @@ const processedLines: ProcessedLogLines = [
17,
18,
{
+ functionID: "function-19",
functionName: "f-5",
isOpen: true,
range: { end: 23, start: 19 },
rowType: RowType.SectionHeader,
},
{
+ commandID: "command-19",
commandName: "shell.exec",
- functionName: "f-5",
+ functionID: "function-19",
isOpen: true,
range: { end: 23, start: 19 },
rowType: RowType.SubsectionHeader,
diff --git a/apps/parsley/src/utils/searchLogs/searchLogs.test.ts b/apps/parsley/src/utils/searchLogs/searchLogs.test.ts
index b88cc9daf..c935d8d5d 100644
--- a/apps/parsley/src/utils/searchLogs/searchLogs.test.ts
+++ b/apps/parsley/src/utils/searchLogs/searchLogs.test.ts
@@ -81,6 +81,7 @@ describe("searchLogs", () => {
const processedLogLines: ProcessedLogLines = [
0,
{
+ functionID: "function-1",
functionName: "test",
isOpen: true,
range: { end: 2, start: 1 },