Skip to content

Commit

Permalink
Query UI: Add tenant box
Browse files Browse the repository at this point in the history
With this commit as tenant box is added to the query UI. It can be used
to specify which tenant to use when making a query.

Signed-off-by: Jacob Baungard Hansen <jacobbaungard@redhat.com>
  • Loading branch information
jacobbaungard committed Nov 10, 2023
1 parent c74a050 commit b530d4a
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -16,6 +16,8 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re

### Added

- [#6867](https://github.com/thanos-io/thanos/pull/6867) Query UI: Tenant input box added to the Query UI, in order to be able to specify which tenant the query should use.

### Changed

### Removed
Expand Down
2 changes: 1 addition & 1 deletion cmd/thanos/query.go
Expand Up @@ -717,7 +717,7 @@ func runQuery(

ins := extpromhttp.NewTenantInstrumentationMiddleware(tenantHeader, defaultTenant, reg, nil)
// TODO(bplotka in PR #513 review): pass all flags, not only the flags needed by prefix rewriting.
ui.NewQueryUI(logger, endpoints, webExternalPrefix, webPrefixHeaderName, alertQueryURL).Register(router, ins)
ui.NewQueryUI(logger, endpoints, webExternalPrefix, webPrefixHeaderName, alertQueryURL, tenantHeader, defaultTenant).Register(router, ins)

api := apiv1.NewQueryAPI(
logger,
Expand Down
80 changes: 40 additions & 40 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions pkg/ui/query.go
Expand Up @@ -30,10 +30,12 @@ type Query struct {
now func() model.Time
}

func NewQueryUI(logger log.Logger, endpointSet *query.EndpointSet, externalPrefix, prefixHeader, alertQueryURL string) *Query {
func NewQueryUI(logger log.Logger, endpointSet *query.EndpointSet, externalPrefix, prefixHeader, alertQueryURL string, tenantHeader string, defaultTenant string) *Query {
tmplVariables := map[string]string{
"Component": component.Query.String(),
"queryURL": alertQueryURL,
"Component": component.Query.String(),
"queryURL": alertQueryURL,
"tenantHeader": tenantHeader,
"defaultTenant": defaultTenant,
}
runtimeInfo := api.GetRuntimeInfoFunc(logger)

Expand Down
2 changes: 2 additions & 0 deletions pkg/ui/react-app/globals.d.ts
Expand Up @@ -5,5 +5,7 @@ declare global {
jQuery: JQueryStatic;
moment: Moment;
THANOS_QUERY_URL: string;
THANOS_DEFAULT_TENANT: string;
THANOS_TENANT_HEADER: string;
}
}
2 changes: 2 additions & 0 deletions pkg/ui/react-app/public/index.html
Expand Up @@ -18,6 +18,8 @@
<script>
const THANOS_COMPONENT="{{ .Component }}";
const THANOS_QUERY_URL="{{ .queryURL }}";
const THANOS_TENANT_HEADER="{{ .tenantHeader }}";
const THANOS_DEFAULT_TENANT="{{ .defaultTenant }}";
</script>

<!--
Expand Down
2 changes: 2 additions & 0 deletions pkg/ui/react-app/src/globals.ts
Expand Up @@ -3,3 +3,5 @@ import jquery from 'jquery';
window.jQuery = jquery;
window.moment = require('moment');
window.THANOS_QUERY_URL = '';
window.THANOS_TENANT_HEADER = '';
window.THANOS_DEFAULT_TENANT = '';
2 changes: 2 additions & 0 deletions pkg/ui/react-app/src/pages/graph/Panel.test.tsx
Expand Up @@ -25,6 +25,7 @@ const defaultProps: PanelProps = {
engine: 'prometheus',
analyze: false,
disableAnalyzeCheckbox: false,
tenant: 'default-tenant',
},
onOptionsChanged: (): void => {
// Do nothing.
Expand Down Expand Up @@ -101,6 +102,7 @@ describe('Panel', () => {
engine: 'prometheus',
analyze: false,
disableAnalyzeCheckbox: false,
tenant: 'default-tenant',
};
const graphPanel = mount(<Panel {...defaultProps} options={options} />);
const controls = graphPanel.find(GraphControls);
Expand Down
48 changes: 42 additions & 6 deletions pkg/ui/react-app/src/pages/graph/Panel.tsx
Expand Up @@ -30,6 +30,7 @@ import { Store } from '../../thanos/pages/stores/store';
import PathPrefixProps from '../../types/PathPrefixProps';
import { QueryParams } from '../../types/types';
import { parseDuration } from '../../utils';
import { defaultTenant, tenantHeader } from '../../thanos/config';

export interface PanelProps {
id: string;
Expand Down Expand Up @@ -76,6 +77,7 @@ export interface PanelOptions {
engine: string;
analyze: boolean;
disableAnalyzeCheckbox: boolean;
tenant: string;
}

export enum PanelType {
Expand All @@ -98,6 +100,7 @@ export const PanelDefaultOptions: PanelOptions = {
engine: '',
analyze: false,
disableAnalyzeCheckbox: false,
tenant: '',
};

class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
Expand Down Expand Up @@ -132,6 +135,7 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
this.handleChangeAnalyze = this.handleChangeAnalyze.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleChangeTenant = this.handleChangeTenant.bind(this);
}
componentDidUpdate({ options: prevOpts }: PanelProps): void {
const {
Expand All @@ -145,6 +149,7 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
usePartialResponse,
engine,
analyze,
tenant,
// TODO: Add support for Store Matches
} = this.props.options;
if (
Expand All @@ -157,7 +162,8 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
prevOpts.usePartialResponse !== usePartialResponse ||
prevOpts.forceTracing !== forceTracing ||
prevOpts.engine !== engine ||
prevOpts.analyze !== analyze
prevOpts.analyze !== analyze ||
prevOpts.tenant !== tenant
// Check store matches
) {
this.executeQuery();
Expand Down Expand Up @@ -215,25 +221,35 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
params.append('max_source_resolution', this.props.options.maxSourceResolution);
params.append('engine', this.props.options.engine);
params.append('analyze', this.props.options.analyze.toString());
params.append('tenant', this.props.options.tenant);
// TODO path prefix here and elsewhere.
break;
case 'table':
path = '/api/v1/query';
params.append('time', endTime.toString());
params.append('engine', this.props.options.engine);
params.append('analyze', this.props.options.analyze.toString());
params.append('tenant', this.props.options.tenant);
break;
default:
throw new Error('Invalid panel type "' + this.props.options.type + '"');
}

// Create request headers
const requestHeaders: HeadersInit = new Headers();
requestHeaders.set('Content-Type', 'application/json');

if (this.props.options.forceTracing) {
requestHeaders.set('X-Thanos-Force-Tracing', 'true');
}

if (this.props.options.tenant.length > 0) {
requestHeaders.set(tenantHeader, this.props.options.tenant);
}

fetch(`${this.props.pathPrefix}${path}?${params}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
// Conditionally add the header if the checkbox is enabled
...(this.props.options.forceTracing ? { 'X-Thanos-Force-Tracing': 'true' } : {}),
},
headers: requestHeaders,
cache: 'no-store',
credentials: 'same-origin',
signal: abortController.signal,
Expand Down Expand Up @@ -359,6 +375,10 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
this.setOptions({ analyze: event.target.checked });
};

handleChangeTenant = (event: React.ChangeEvent<HTMLInputElement>): void => {
this.setOptions({ tenant: event.target.value });
};

handleMouseEnter = () => {
this.setState({ isHovered: true });
};
Expand Down Expand Up @@ -536,6 +556,22 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
<option value="prometheus">Prometheus</option>
<option value="thanos">Thanos</option>
</Input>
<Label style={{ marginLeft: '10px', display: 'inline-block' }} className="control-label">
Tenant
</Label>
<Input
style={{
width: 'auto',
marginLeft: '10px',
display: 'inline-block',
}}
id={`tenant=${id}`}
type="text"
bsSize="sm"
onChange={this.handleChangeTenant}
placeholder={`${defaultTenant}`}
value={options.tenant}
></Input>
</div>
<div className="float-right" onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<Checkbox
Expand Down
12 changes: 12 additions & 0 deletions pkg/ui/react-app/src/thanos/config.ts
@@ -1,6 +1,18 @@
declare const THANOS_QUERY_URL: string;
declare const THANOS_TENANT_HEADER: string;
declare const THANOS_DEFAULT_TENANT: string;

export let queryURL = THANOS_QUERY_URL;
if (queryURL === '' || queryURL === '{{ .queryURL }}') {
queryURL = 'http://localhost:10902';
}

export let defaultTenant = THANOS_DEFAULT_TENANT;
if (defaultTenant === '' || defaultTenant === '{{ .defaultTenant }}') {
defaultTenant = 'default-tenant';
}

export let tenantHeader = THANOS_TENANT_HEADER;
if (tenantHeader === '' || tenantHeader === '{{ .tenantHeader }}') {
tenantHeader = 'thanos-tenant';
}
5 changes: 5 additions & 0 deletions pkg/ui/react-app/src/utils/index.ts
Expand Up @@ -233,6 +233,9 @@ export const parseOption = (param: string): Partial<PanelOptions> => {

case 'analyze':
return { analyze: decodedValue === '1' };

case 'tenant':
return { tenant: decodedValue };
}
return {};
};
Expand All @@ -258,6 +261,7 @@ export const toQueryString = ({ key, options }: PanelMeta): string => {
storeMatches,
engine,
analyze,
tenant,
} = options;
const time = isPresent(endTime) ? formatTime(endTime) : false;
const urlParams = [
Expand All @@ -271,6 +275,7 @@ export const toQueryString = ({ key, options }: PanelMeta): string => {
formatWithKey('store_matches', JSON.stringify(storeMatches, ['name'])),
formatWithKey('engine', engine),
formatWithKey('analyze', analyze ? 1 : 0),
formatWithKey('tenant', tenant),
time ? `${formatWithKey('end_input', time)}&${formatWithKey('moment_input', time)}` : '',
isPresent(resolution) ? formatWithKey('step_input', resolution) : '',
];
Expand Down
7 changes: 5 additions & 2 deletions pkg/ui/react-app/src/utils/utils.test.ts
Expand Up @@ -223,6 +223,7 @@ describe('Utils', () => {
storeMatches: [],
engine: 'prometheus',
analyze: false,
tenant: 'default-tenant',
},
},
{
Expand All @@ -240,11 +241,12 @@ describe('Utils', () => {
storeMatches: stores,
engine: 'prometheus',
analyze: false,
tenant: 'default-tenant',
},
},
];
const query =
'?g0.expr=rate(node_cpu_seconds_total%7Bmode%3D%22system%22%7D%5B1m%5D)&g0.tab=0&g0.stacked=0&g0.range_input=1h&g0.max_source_resolution=raw&g0.deduplicate=1&g0.partial_response=0&g0.store_matches=%5B%5D&g0.engine=prometheus&g0.analyze=0&g0.end_input=2019-10-25%2023%3A37%3A00&g0.moment_input=2019-10-25%2023%3A37%3A00&g1.expr=node_filesystem_avail_bytes&g1.tab=1&g1.stacked=0&g1.range_input=1h&g1.max_source_resolution=auto&g1.deduplicate=0&g1.partial_response=1&g1.store_matches=%5B%7B%22name%22%3A%22thanos_sidecar_one%3A10901%22%7D%5D&g1.engine=prometheus&g1.analyze=0';
'?g0.expr=rate(node_cpu_seconds_total%7Bmode%3D%22system%22%7D%5B1m%5D)&g0.tab=0&g0.stacked=0&g0.range_input=1h&g0.max_source_resolution=raw&g0.deduplicate=1&g0.partial_response=0&g0.store_matches=%5B%5D&g0.engine=prometheus&g0.analyze=0&g0.tenant=default-tenant&g0.end_input=2019-10-25%2023%3A37%3A00&g0.moment_input=2019-10-25%2023%3A37%3A00&g1.expr=node_filesystem_avail_bytes&g1.tab=1&g1.stacked=0&g1.range_input=1h&g1.max_source_resolution=auto&g1.deduplicate=0&g1.partial_response=1&g1.store_matches=%5B%7B%22name%22%3A%22thanos_sidecar_one%3A10901%22%7D%5D&g1.engine=prometheus&g1.analyze=0&g1.tenant=default-tenant';

describe('decodePanelOptionsFromQueryString', () => {
it('returns [] when query is empty', () => {
Expand Down Expand Up @@ -338,10 +340,11 @@ describe('Utils', () => {
engine: 'prometheus',
analyze: false,
disableAnalyzeCheckbox: false,
tenant: 'default-tenant',
},
})
).toEqual(
'g0.expr=foo&g0.tab=0&g0.stacked=1&g0.range_input=0s&g0.max_source_resolution=raw&g0.deduplicate=1&g0.partial_response=0&g0.store_matches=%5B%5D&g0.engine=prometheus&g0.analyze=0&g0.step_input=1'
'g0.expr=foo&g0.tab=0&g0.stacked=1&g0.range_input=0s&g0.max_source_resolution=raw&g0.deduplicate=1&g0.partial_response=0&g0.store_matches=%5B%5D&g0.engine=prometheus&g0.analyze=0&g0.tenant=default-tenant&g0.step_input=1'
);
});
});
Expand Down

0 comments on commit b530d4a

Please sign in to comment.