Merge "ui: Improve peformance of heap graph context options" into main
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index e2f66fc..ecef372 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -26,7 +26,6 @@
"src/tracing:unittests",
"src/profiling:unittests",
"src/profiling/symbolizer:unittests",
- "src/android_sdk/perfetto_sdk_for_jni:unittests",
]
if ((is_linux || is_android) && !perfetto_build_with_embedder) {
@@ -87,4 +86,9 @@
perfetto_unittests_targets += [ "src/traced_relay:unittests" ]
}
+if (enable_perfetto_android_java_sdk) {
+ perfetto_unittests_targets +=
+ [ "src/android_sdk/perfetto_sdk_for_jni:unittests" ]
+}
+
perfetto_unittests_targets += [ "src/trace_redaction:unittests" ]
diff --git a/include/perfetto/base/logging.h b/include/perfetto/base/logging.h
index a882e2e..b358da1 100644
--- a/include/perfetto/base/logging.h
+++ b/include/perfetto/base/logging.h
@@ -25,8 +25,18 @@
#include "perfetto/base/export.h"
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
#if PERFETTO_BUILDFLAG(PERFETTO_FORCE_DCHECK_ON)
diff --git a/include/perfetto/public/te_category_macros.h b/include/perfetto/public/te_category_macros.h
index a9f5071..6dc0e81 100644
--- a/include/perfetto/public/te_category_macros.h
+++ b/include/perfetto/public/te_category_macros.h
@@ -59,8 +59,19 @@
// it would expand to zero arguments, because the behavior is compiler
// dependent.
-#if defined(__GNUC__)
+#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
#define PERFETTO_I_TE_CAT_DESCRIPTION_GET(D, ...) D
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index ac0af46..2031cf7 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -26,9 +26,19 @@
#include "perfetto/tracing/string_helpers.h"
#include "perfetto/tracing/track_event_category_registry.h"
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
// Defines data structures for backing a category registry.
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 8db70a6..de2c75f 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -255,9 +255,19 @@
#define PERFETTO_TRACK_EVENT_STATIC_STORAGE() \
PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(perfetto)
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
// Begin a slice under |category| with the title |name|. Both strings must be
diff --git a/include/perfetto/tracing/track_event_legacy.h b/include/perfetto/tracing/track_event_legacy.h
index fb5922b..cd55f85 100644
--- a/include/perfetto/tracing/track_event_legacy.h
+++ b/include/perfetto/tracing/track_event_legacy.h
@@ -31,9 +31,19 @@
#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 0
#endif
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
// ----------------------------------------------------------------------------
diff --git a/src/android_sdk/perfetto_sdk_for_jni/BUILD.gn b/src/android_sdk/perfetto_sdk_for_jni/BUILD.gn
index 6255a66..865c00b 100644
--- a/src/android_sdk/perfetto_sdk_for_jni/BUILD.gn
+++ b/src/android_sdk/perfetto_sdk_for_jni/BUILD.gn
@@ -1,5 +1,8 @@
+import("../../../gn/perfetto.gni")
import("../../../gn/test.gni")
+assert(enable_perfetto_android_java_sdk)
+
source_set("perfetto_sdk_for_jni_public") {
sources = [ "tracing_sdk.h" ]
}
diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h
index 94bc646..2f556a1 100644
--- a/src/trace_processor/storage/metadata.h
+++ b/src/trace_processor/storage/metadata.h
@@ -78,9 +78,19 @@
F(kMulti, "multi")
// clang-format
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
#define PERFETTO_TP_META_TYPE_ENUM(varname, ...) varname
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index ea03e42..508b5ea 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -496,9 +496,19 @@
kAnalysis
};
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
#if defined(__GNUC__) || defined(__clang__)
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Fix 'error: #pragma system_header ignored in main file' for clang in Google3.
+#pragma clang diagnostic ignored "-Wpragma-system-header-outside-header"
+#endif
+
+// Ignore GCC warning about a missing argument for a variadic macro parameter.
#pragma GCC system_header
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
#endif
// Declares an enum of literals (one for each stat). The enum values of each
diff --git a/src/trace_processor/util/streaming_line_reader.cc b/src/trace_processor/util/streaming_line_reader.cc
index 6ec28b9..55a5a01 100644
--- a/src/trace_processor/util/streaming_line_reader.cc
+++ b/src/trace_processor/util/streaming_line_reader.cc
@@ -16,7 +16,6 @@
#include "src/trace_processor/util/streaming_line_reader.h"
-#include <sys/types.h>
#include <cstddef>
#include <utility>
#include <vector>
@@ -48,7 +47,7 @@
// Unless we got very lucky, the last line in the chunk just written will be
// incomplete. Move it to the beginning of the buffer so it gets glued
// together on the next {Begin,End}Write() call.
- buf_.erase(buf_.begin(), buf_.begin() + static_cast<ssize_t>(consumed));
+ buf_.erase(buf_.begin(), buf_.begin() + static_cast<int64_t>(consumed));
}
size_t StreamingLineReader::Tokenize(base::StringView input) {
diff --git a/ui/src/components/tracks/breakdown_tracks.ts b/ui/src/components/tracks/breakdown_tracks.ts
index c379477..66fc853 100644
--- a/ui/src/components/tracks/breakdown_tracks.ts
+++ b/ui/src/components/tracks/breakdown_tracks.ts
@@ -12,10 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../../base/logging';
import {sqliteString} from '../../base/string_utils';
import {uuidv4} from '../../base/uuid';
-import {runQuery} from '../../components/query_table/queries';
import {DatasetSliceTrack} from '../../components/tracks/dataset_slice_track';
import {
createQueryCounterTrack,
@@ -24,7 +22,7 @@
import {createQuerySliceTrack} from '../../components/tracks/query_slice_track';
import {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
-import {ColumnType} from '../../trace_processor/query_result';
+import {ColumnType, NUM} from '../../trace_processor/query_result';
/**
* Aggregation types for the BreakdownTracks.
@@ -233,22 +231,22 @@
async createTracks() {
if (this.modulesClause !== '') {
- this.props.trace.engine.query(this.modulesClause);
+ await this.props.trace.engine.query(this.modulesClause);
}
if (this.props.aggregationType !== BreakdownTrackAggType.COUNT) {
- this.props.trace.engine.query(`
- CREATE OR REPLACE PERFETTO FUNCTION _ui_dev_perfetto_breakdown_tracks_is_spans_overlapping(
- ts1 LONG,
- ts_end1 LONG,
- ts2 LONG,
- ts_end2 LONG)
- RETURNS BOOL
- AS
- SELECT (IIF($ts1 < $ts2, $ts2, $ts1) < IIF($ts_end1 < $ts_end2, $ts_end1, $ts_end2));
+ await this.props.trace.engine.query(`
+ CREATE OR REPLACE PERFETTO FUNCTION _ui_dev_perfetto_breakdown_tracks_is_spans_overlapping(
+ ts1 LONG,
+ ts_end1 LONG,
+ ts2 LONG,
+ ts_end2 LONG)
+ RETURNS BOOL
+ AS
+ SELECT (IIF($ts1 < $ts2, $ts2, $ts1) < IIF($ts_end1 < $ts_end2, $ts_end1, $ts_end2));
- ${this.getIntervals()}
- `);
+ ${this.getIntervals()}
+ `);
}
const rootTrackNode = await this.createCounterTrackNode(
@@ -283,30 +281,29 @@
const joinClause = this.getTrackSpecificJoinClause(trackType);
const query = `
- ${this.modulesClause}
+ ${this.modulesClause}
- SELECT DISTINCT ${currColName}
- FROM ${this.props.aggregation.tableName}
- ${joinClause !== undefined ? joinClause : ''}
- ${filters.length > 0 ? `WHERE ${buildFilterSqlClause(filters)}` : ''}`;
+ SELECT DISTINCT ${currColName}
+ FROM ${this.props.aggregation.tableName}
+ ${joinClause !== undefined ? joinClause : ''}
+ ${filters.length > 0 ? `WHERE ${buildFilterSqlClause(filters)}` : ''}
+ `;
- const res = await runQuery(query, this.props.trace.engine);
+ const res = await this.props.trace.engine.query(query);
- if (res.error) {
- throw Error(`Track hierarchy query error: ${res.error}`);
- }
+ for (const iter = res.iter({}); iter.valid(); iter.next()) {
+ const colRaw = iter.get(currColName);
+ const colValue = colRaw === null ? 'NULL' : colRaw.toString();
+ const title = colValue;
- res.rows.forEach(async (row) => {
const newFilters = [
...filters,
{
columnName: currColName,
- value: row[currColName]?.toString(),
+ value: colValue,
},
];
- const title = `${row[currColName]?.toString()}`;
-
let currNode;
let nextTrackType = trackType;
let nextColIndex = colIndex + 1;
@@ -353,7 +350,7 @@
nextColIndex,
nextTrackType,
);
- });
+ }
}
private getTrackSpecificJoinClause(trackType: BreakdownTrackType) {
@@ -406,19 +403,11 @@
}
private async getCounterTrackSortOrder(filtersClause: string) {
- const counts = await runQuery(
- `
- SELECT MAX(value) as max_value FROM
- (
- ${this.getAggregationQuery(filtersClause)}
- )
- `,
- this.props.trace.engine,
- );
-
- return Number.parseInt(
- assertExists(counts.rows[0]['max_value']).toString(),
- );
+ const aggregationQuery = this.getAggregationQuery(filtersClause);
+ const result = await this.props.trace.engine.query(`
+ SELECT MAX(value) as max_value FROM (${aggregationQuery})
+ `);
+ return result.firstRow({max_value: NUM}).max_value;
}
private async createCounterTrackNode(title: string, newFilters: Filter[]) {
diff --git a/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
index bbcba70..c5ec9a6 100644
--- a/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
@@ -24,9 +24,26 @@
import {MenuItem} from '../../widgets/menu';
import {Icons} from '../../base/semantic_icons';
import {VisViewSource} from './data_visualiser/view_source';
+import {PopupMenu} from '../../widgets/menu';
+import {createModal} from './query_builder/builder';
+import {
+ StdlibTableAttrs,
+ StdlibTableNode,
+ StdlibTableSource,
+} from './query_builder/sources/stdlib_table';
+import {
+ SlicesSource,
+ SlicesSourceAttrs,
+ SlicesSourceNode,
+} from './query_builder/sources/slices_source';
+import {
+ SqlSource,
+ SqlSourceAttrs,
+ SqlSourceNode,
+} from './query_builder/sources/sql_source';
export interface ExplorePageState {
- rootNode?: QueryNode; // Root Query Node
+ rootNodes: QueryNode[];
selectedNode?: QueryNode; // Selected Query Node on which to perform actions
activeViewSource?: VisViewSource; // View Source of activeQueryNode
mode: ExplorePageModes;
@@ -59,6 +76,82 @@
});
}
+ addSourcePopupMenu(attrs: ExplorePageAttrs): m.Children {
+ const {trace, state} = attrs;
+ const sqlModules = attrs.sqlModulesPlugin.getSqlModules();
+ return [
+ m(MenuItem, {
+ label: 'Standard library table',
+ onclick: async () => {
+ const stdlibTableAttrs: StdlibTableAttrs = {
+ filters: [],
+ sourceCols: [],
+ groupByColumns: [],
+ aggregations: [],
+ trace,
+ sqlModules,
+ modal: () =>
+ createModal(
+ 'Standard library table',
+ () => m(StdlibTableSource, stdlibTableAttrs),
+ () => {
+ const newNode = new StdlibTableNode(stdlibTableAttrs);
+ state.rootNodes.push(newNode);
+ state.selectedNode = newNode;
+ },
+ ),
+ };
+ // Adding trivial modal to open the table selection.
+ createModal(
+ 'Standard library table',
+ () => m(StdlibTableSource, stdlibTableAttrs),
+ () => {},
+ );
+ },
+ }),
+ m(MenuItem, {
+ label: 'Custom slices',
+ onclick: () => {
+ const newSimpleSlicesAttrs: SlicesSourceAttrs = {
+ sourceCols: [],
+ filters: [],
+ groupByColumns: [],
+ aggregations: [],
+ };
+ createModal(
+ 'Slices',
+ () => m(SlicesSource, newSimpleSlicesAttrs),
+ () => {
+ const newNode = new SlicesSourceNode(newSimpleSlicesAttrs);
+ state.rootNodes.push(newNode);
+ state.selectedNode = newNode;
+ },
+ );
+ },
+ }),
+ m(MenuItem, {
+ label: 'Custom SQL',
+ onclick: () => {
+ const newSqlSourceAttrs: SqlSourceAttrs = {
+ sourceCols: [],
+ filters: [],
+ groupByColumns: [],
+ aggregations: [],
+ };
+ createModal(
+ 'SQL',
+ () => m(SqlSource, newSqlSourceAttrs),
+ () => {
+ const newNode = new SqlSourceNode(newSqlSourceAttrs);
+ state.rootNodes.push(newNode);
+ state.selectedNode = newNode;
+ },
+ );
+ },
+ }),
+ ];
+ }
+
view({attrs}: m.CVnode<ExplorePageAttrs>) {
const {trace, state} = attrs;
@@ -69,14 +162,29 @@
m('h1', `${ExplorePageModeToLabel[state.mode]}`),
m('span', {style: {flexGrow: 1}}),
state.mode === ExplorePageModes.QUERY_BUILDER
- ? m(Button, {
- label: 'Clear All Query Nodes',
- intent: Intent.Primary,
- onclick: () => {
- state.rootNode = undefined;
- state.selectedNode = undefined;
- },
- })
+ ? m(
+ '',
+ m(
+ PopupMenu,
+ {
+ trigger: m(Button, {
+ label: 'Add new node',
+ icon: Icons.Add,
+ intent: Intent.Primary,
+ }),
+ },
+ this.addSourcePopupMenu(attrs),
+ ),
+ m(Button, {
+ label: 'Clear All Query Nodes',
+ intent: Intent.Primary,
+ onclick: () => {
+ state.rootNodes = [];
+ state.selectedNode = undefined;
+ },
+ style: {marginLeft: '10px'},
+ }),
+ )
: m(Button, {
label: 'Back to Query Builder',
intent: Intent.Primary,
@@ -91,7 +199,7 @@
trace,
sqlModules: attrs.sqlModulesPlugin.getSqlModules(),
onRootNodeCreated(arg) {
- state.rootNode = arg;
+ state.rootNodes.push(arg);
state.selectedNode = arg;
},
onNodeSelected(arg) {
@@ -99,11 +207,12 @@
},
visualiseDataMenuItems: (node: QueryNode) =>
this.renderVisualiseDataMenuItems(node, state),
- rootNode: state.rootNode,
+ rootNodes: state.rootNodes,
selectedNode: state.selectedNode,
+ addSourcePopupMenu: () => this.addSourcePopupMenu(attrs),
}),
state.mode === ExplorePageModes.DATA_VISUALISER &&
- state.rootNode &&
+ state.rootNodes.length !== 0 &&
m(DataVisualiser, {
trace,
state,
diff --git a/ui/src/plugins/dev.perfetto.ExplorePage/index.ts b/ui/src/plugins/dev.perfetto.ExplorePage/index.ts
index a1a2fa1..6746248 100644
--- a/ui/src/plugins/dev.perfetto.ExplorePage/index.ts
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/index.ts
@@ -27,6 +27,7 @@
// trace.
private readonly state: ExplorePageState = {
mode: ExplorePageModes.QUERY_BUILDER,
+ rootNodes: [],
};
async onTraceLoad(trace: Trace): Promise<void> {
diff --git a/ui/src/plugins/dev.perfetto.ExplorePage/query_builder/builder.ts b/ui/src/plugins/dev.perfetto.ExplorePage/query_builder/builder.ts
index 670c48b..b2cd824 100644
--- a/ui/src/plugins/dev.perfetto.ExplorePage/query_builder/builder.ts
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/query_builder/builder.ts
@@ -39,18 +39,19 @@
export interface QueryBuilderTable {
name: string;
asSqlTable: SqlTable;
- columnOptions: ColumnControllerRow[];
+ columnOptions: ColumnControllerRow;
sql: string;
}
export interface QueryBuilderAttrs extends PageWithTraceAttrs {
readonly sqlModules: SqlModules;
- readonly rootNode?: QueryNode;
+ readonly rootNodes: QueryNode[];
readonly selectedNode?: QueryNode;
readonly onRootNodeCreated: (node: QueryNode) => void;
- readonly onNodeSelected: (node: QueryNode) => void;
+ readonly onNodeSelected: (node?: QueryNode) => void;
readonly visualiseDataMenuItems: (node: QueryNode) => m.Children;
+ readonly addSourcePopupMenu: () => m.Children;
}
interface NodeAttrs {
@@ -81,103 +82,7 @@
export class QueryBuilder implements m.ClassComponent<QueryBuilderAttrs> {
view({attrs}: m.CVnode<QueryBuilderAttrs>) {
- const {
- trace,
- sqlModules,
- rootNode,
- onRootNodeCreated,
- onNodeSelected,
- selectedNode,
- } = attrs;
-
- const chooseSourceButton = (): m.Child => {
- return m(
- PopupMenu,
- {
- trigger: m(Button, {
- icon: Icons.Add,
- intent: Intent.Primary,
- style: {
- height: '100px',
- width: '100px',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- fontSize: '48px',
- },
- }),
- },
- m(MenuItem, {
- label: 'Standard library table',
- onclick: async () => {
- const attrs: StdlibTableAttrs = {
- filters: [],
- sourceCols: [],
- groupByColumns: [],
- aggregations: [],
- trace,
- sqlModules,
- modal: () =>
- createModal(
- 'Standard library table',
- () => m(StdlibTableSource, attrs),
- () => {
- const newNode = new StdlibTableNode(attrs);
- onRootNodeCreated(newNode);
- onNodeSelected(newNode);
- },
- ),
- };
- // Adding trivial modal to open the table selection.
- createModal(
- 'Standard library table',
- () => m(StdlibTableSource, attrs),
- () => {},
- );
- },
- }),
- m(MenuItem, {
- label: 'Custom slices',
- onclick: () => {
- const newSimpleSlicesAttrs: SlicesSourceAttrs = {
- sourceCols: [],
- filters: [],
- groupByColumns: [],
- aggregations: [],
- };
- createModal(
- 'Slices',
- () => m(SlicesSource, newSimpleSlicesAttrs),
- () => {
- const newNode = new SlicesSourceNode(newSimpleSlicesAttrs);
- onRootNodeCreated(newNode);
- onNodeSelected(newNode);
- },
- );
- },
- }),
- m(MenuItem, {
- label: 'Custom SQL',
- onclick: () => {
- const newSqlSourceAttrs: SqlSourceAttrs = {
- sourceCols: [],
- filters: [],
- groupByColumns: [],
- aggregations: [],
- };
- createModal(
- 'SQL',
- () => m(SqlSource, newSqlSourceAttrs),
- () => {
- const newNode = new SqlSourceNode(newSqlSourceAttrs);
- onRootNodeCreated(newNode);
- onNodeSelected(newNode);
- },
- );
- },
- }),
- );
- };
+ const {trace, rootNodes, onNodeSelected, selectedNode} = attrs;
const renderNodeActions = (curNode: QueryNode) => {
return m(
@@ -199,12 +104,11 @@
'Standard library table',
() => m(StdlibTableSource, attrsCopy as StdlibTableAttrs),
() => {
- curNode = new StdlibTableNode(
+ // TODO: Support editing non root nodes.
+ rootNodes[rootNodes.indexOf(curNode)] = new StdlibTableNode(
attrsCopy as StdlibTableAttrs,
);
onNodeSelected(curNode);
- // TODO: remove this hack after handling multiple roots
- onRootNodeCreated(curNode);
},
);
curNode = new StdlibTableNode(attrsCopy as StdlibTableAttrs);
@@ -214,12 +118,10 @@
'Slices',
() => m(SlicesSource, attrsCopy as SlicesSourceAttrs),
() => {
- curNode = new SlicesSourceNode(
- attrsCopy as SlicesSourceAttrs,
- );
+ // TODO: Support editing non root nodes.
+ rootNodes[rootNodes.indexOf(curNode)] =
+ new SlicesSourceNode(attrsCopy as SlicesSourceAttrs);
onNodeSelected(curNode);
- // TODO: remove this hack after handling multiple roots
- onRootNodeCreated(curNode);
},
);
break;
@@ -228,45 +130,88 @@
'SQL',
() => m(SqlSource, attrsCopy as SqlSourceAttrs),
() => {
- curNode = new SqlSourceNode(attrsCopy as SqlSourceAttrs);
+ // TODO: Support editing non root nodes.
+ rootNodes[rootNodes.indexOf(curNode)] = new SqlSourceNode(
+ attrsCopy as SqlSourceAttrs,
+ );
onNodeSelected(curNode);
- // TODO: remove this hack after handling multiple roots
- onRootNodeCreated(curNode);
},
);
}
},
}),
+ m(MenuItem, {
+ label: 'Duplicate',
+ onclick: async () => {
+ attrs.rootNodes.push(cloneQueryNode(curNode));
+ },
+ }),
+ m(MenuItem, {
+ label: 'Delete',
+ onclick: async () => {
+ const idx = attrs.rootNodes.indexOf(curNode);
+ if (idx !== -1) {
+ attrs.rootNodes.splice(idx, 1);
+ onNodeSelected(undefined);
+ }
+ },
+ }),
);
};
const renderNodesPanel = (): m.Children => {
const nodes: m.Child[] = [];
- let row = 1;
+ const numRoots = rootNodes.length;
- if (!rootNode) {
+ if (numRoots === 0) {
nodes.push(
- m('', {style: {gridColumn: 3, gridRow: 2}}, chooseSourceButton()),
+ m(
+ '',
+ {style: {gridColumn: 3, gridRow: 2}},
+ m(
+ PopupMenu,
+ {
+ trigger: m(Button, {
+ icon: Icons.Add,
+ intent: Intent.Primary,
+ style: {
+ height: '100px',
+ width: '100px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ fontSize: '48px',
+ },
+ }),
+ },
+ attrs.addSourcePopupMenu(),
+ ),
+ ),
);
} else {
- let curNode: QueryNode | undefined = rootNode;
- while (curNode) {
- const localCurNode = curNode;
- nodes.push(
- m(
- '',
- {style: {display: 'flex', gridColumn: 3, gridRow: row}},
- m(NodeBox, {
- node: localCurNode,
- isSelected: selectedNode === localCurNode,
- onNodeSelected,
- }),
- renderNodeActions(curNode),
- ),
- );
- row++;
- curNode = curNode.nextNode;
- }
+ let col = 1;
+ rootNodes.forEach((rootNode) => {
+ let row = 1;
+ let curNode: QueryNode | undefined = rootNode;
+ while (curNode) {
+ const localCurNode = curNode;
+ nodes.push(
+ m(
+ '',
+ {style: {display: 'flex', gridColumn: col, gridRow: row}},
+ m(NodeBox, {
+ node: localCurNode,
+ isSelected: selectedNode === localCurNode,
+ onNodeSelected,
+ }),
+ renderNodeActions(curNode),
+ ),
+ );
+ row++;
+ curNode = curNode.nextNode;
+ }
+ col += 1;
+ });
}
return m(
@@ -274,7 +219,7 @@
{
style: {
display: 'grid',
- gridTemplateColumns: 'repeat(5, 1fr)',
+ gridTemplateColumns: `repeat(${numRoots} - 1, 1fr)`,
gridTemplateRows: 'repeat(3, 1fr)',
gap: '10px',
},
@@ -316,3 +261,15 @@
content,
});
};
+
+function cloneQueryNode(node: QueryNode): QueryNode {
+ const attrsCopy = node.getState();
+ switch (node.type) {
+ case NodeType.kStdlibTable:
+ return new StdlibTableNode(attrsCopy as StdlibTableAttrs);
+ case NodeType.kSimpleSlices:
+ return new SlicesSourceNode(attrsCopy as SlicesSourceAttrs);
+ case NodeType.kSqlSource:
+ return new SqlSourceNode(attrsCopy as SqlSourceAttrs);
+ }
+}