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

Added script switch button to the query panel. #41

Merged
merged 5 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
46 changes: 46 additions & 0 deletions src/pxl_scripts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2018- The Pixie Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

const customQuery = require('./pxl_scripts/custom-query.json');
const exampleQuery = require('pxl_scripts/example-query.json');
const httpDataFiltered = require('pxl_scripts/http-data-filtered.json');
const httpErrorsPerService = require('pxl_scripts/http-errors-per-service.json');

interface Script {
name: string;
description: string;
script: string;
}

// Load predefined scripts
const scriptsRaw: Script[] = [customQuery, exampleQuery, httpDataFiltered, httpErrorsPerService];

// Make a map for easier access of scripts
export const scripts = new Map(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general rule (only writing it once since it's the same thing elsewhere):

Even though TypeScript can often figure out the type of something for you, it's still a good idea to specify it explicitly when:

  • The symbol is exported and isn't a primitive or otherwise trivial type
  • The type may not be instantly obvious to the reader even if TypeScript inferred it correctly (this is clearly a Map, but I have to read surrounding code to realize it's actually a Map<string, Script> and not a Map<string, string>)
  • The contents of the object might be complex, and specifying the type will let the compiler tell you if you didn't fill it out properly.

In this specific case, export const scripts = new Map<string, Script>(...) would make it nice and clear.

scriptsRaw.map((script) => {
return [script.name, script];
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is a good candidate for shorthand syntax.

scriptsRaw.map((script) => [script.name, script]);

);

// Construct options list which is injested by Select component in
export const options = scriptsRaw.map((script: Script) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options is pretty vague here, so when it gets imported into query_editor.tsx, it can be unclear what options means, especially if there are potentially other dropdowns. Let's just rename this to scriptOptions.

return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: shorthand syntax also works for objects if you wrap them in parentheses, like this:

export const scriptOptions: Array<{ label: string, description: string }> = scriptsRaw.map((script) => ({
  label: script.name,
  description: script.description,
}));

label: script.name,
description: script.description,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a nice, small follow-up PR is to figure out how to incorporate/show this description when you're looking through scripts in the dropdown. This is just a UI improvement thing though, so just a nice-to-have (and shouldn't be done in this PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already done through Grafana UI component. Additionally it can display images near the selections, but I don't think we need that.

};
});
5 changes: 5 additions & 0 deletions src/pxl_scripts/custom-query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Custom Query",
"description": "Write your own pxl query.",
"script": ""
}
5 changes: 5 additions & 0 deletions src/pxl_scripts/example-query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Example Query",
"description": "Example query from Pixie documentation.",
"script": "# Import Pixie's module for querying data.\nimport px\n# Load data from Pixie's \\`http_events\\` table into a Dataframe.\ndf = px.DataFrame(table='http_events', start_time=__time_from)\n# Add K8s metadata context.\ndf.service = df.ctx['service']\ndf.namespace = df.ctx['namespace']\n# Bin the 'time_' column using the interval provided by Grafana.\ndf.timestamp = px.bin(df.time_, __interval)\n# Group data by unique pairings of 'timestamp' and 'service'\n# and count the total number of requests per unique pairing.\nper_ns_df = df.groupby(['timestamp', 'service']).agg(\n throughput_total=('latency', px.count)\n )\n# Calculate throughput by dividing # of requests by the time interval.\nper_ns_df.request_throughput = per_ns_df.throughput_total / __interval\nper_ns_df.request_throughput = per_ns_df.request_throughput * 1e9\n# Rename 'timestamp' column to 'time_'. The Grafana plugin expects a 'time_'\n# column to display data in a Graph or Time series.\nper_ns_df.time_ = per_ns_df.timestamp\n# Output select columns of the DataFrame.\npx.display(per_ns_df['time_', 'service', 'request_throughput'])\n"
}
5 changes: 5 additions & 0 deletions src/pxl_scripts/http-data-filtered.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Raw HTTP Events (Long Format)",
"description": "Query for raw HTTP events. Shouldn't be used with time series.",
"script": "'''\nThis query outputs a table of HTTP events (request and response pairs).\nIt produces output identical to Pixie's \\`px/http_data_filtered\\` script in the Live UI.\n\nTo filter the HTTP events, uncomment lines 41-43. Alternatively, use Grafana's\ntable column filtering feature:\nhttps://grafana.com/docs/grafana/latest/visualizations/table/filter-table-columns/\n\nThis query is for use with Grafana's Pixie Datasource Plugin only,\nas it uses Grafana macros for adding Grafana dashboard context.\n'''\n\n# Import Pixie's module for querying data.\nimport px\n\n# Import HTTP events table.\ndf = px.DataFrame(table='http_events', start_time=__time_from)\n\n# Add columns for service, pod info.\ndf.svc = df.ctx['service']\ndf.pod = df.ctx['pod']\ndf = df.drop('upid')\n\n# EXAMPLE OPTIONAL FILTERS\n#df = df[px.contains(df.svc, 'catalogue')]\n#df = df[px.contains(df.pod, 'catalogue')]\n#df = df[df.req_path == '/healthz']\n\n# Avoid conversion to wide format\ndf.timestamp = df.time_\ndf = df.drop(columns=['time_'])\n\n# Keep only the selected columns (and order them in the following order)\ndf = df[['timestamp', 'remote_addr', 'remote_port', 'req_method', 'req_path', 'resp_status', 'resp_body', 'latency', 'svc', 'pod']]\n\n# Output the DataFrame\npx.display(df)\n"
}
5 changes: 5 additions & 0 deletions src/pxl_scripts/http-errors-per-service.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "HTTP Error Rate by Service (Wide Format)",
"description": "",
"script": "'''\nThis query outputs a table of HTTP error and total request count per service.\n\nThis query is for use with Grafana's Pixie Datasource Plugin only,\nas it uses Grafana macros for adding Grafana dashboard context.\n'''\n\n# Import Pixie's module for querying data.\nimport px\n\n# Import HTTP events table.\ndf = px.DataFrame(table='http_events', start_time=__time_from)\n\n# Add columns for service, namespace info.\ndf.namespace = df.ctx['namespace']\ndf.service = df.ctx['service']\n\n# Filter out requests that don't have a service defined.\ndf = df[df.service != '']\n\n# Filter out requests from the Pixie (pl) namespace.\ndf = df[df.namespace != 'pl']\n\n# Add column for HTTP response status errors.\ndf.error = df.resp_status >= 400\n\n# Group HTTP events by service, counting errors and total HTTP events.\ndf = df.groupby(['service']).agg(\n error_count=('error', px.sum),\n total_requests=('resp_status', px.count)\n)\n\n# Output the DataFrame.\npx.display(df)\n"
}
19 changes: 17 additions & 2 deletions src/query_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
import defaults from 'lodash/defaults';

import React, { ChangeEvent, PureComponent } from 'react';
import { TextArea } from '@grafana/ui';
import { QueryEditorProps } from '@grafana/data';
import { TextArea, Select } from '@grafana/ui';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { DataSource } from './datasource';
import { options, scripts } from 'pxl_scripts';
import { defaultQuery, PixieDataSourceOptions, PixieDataQuery } from './types';

type Props = QueryEditorProps<DataSource, PixieDataQuery, PixieDataSourceOptions>;
Expand All @@ -33,12 +34,26 @@ export class QueryEditor extends PureComponent<Props> {
onRunQuery();
}

onSelect(option: SelectableValue<number>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kartik is working on adding some more dropdowns (for example, for column selection, groupbys). Let's name this onSelect something more specific to avoid confusion. Something like: onScriptSelect.

if (option.label != null) {
// Load in predefined scripts to the script text box

const script = scripts.get(option.label);
if (script !== undefined) {
const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, pxlScript: script.script });
onRunQuery();
}
}
}

render() {
const query = defaults(this.props.query, defaultQuery);
const { pxlScript } = query;

return (
<div className="gf-form">
<Select options={options} onChange={this.onSelect.bind(this)} />
<TextArea
id="PxL Script"
name="PxL Script"
Expand Down