aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorValerij Fredriksen <freva@users.noreply.github.com>2022-10-31 09:29:16 +0100
committerGitHub <noreply@github.com>2022-10-31 09:29:16 +0100
commit42d67fa4e4fd247296b1e99bcfe44e1fdcb50834 (patch)
tree2e4da8e825af7d5a91ca728ab1a9cc23ef069b2a
parentdb6c0474107a847db494fbb3152e8179182ae768 (diff)
parent7760654b5e8e0b17838686b3b409be1f7c23b90c (diff)
Merge pull request #24647 from vespa-engine/freva/qb-input
QueryBuilder: Use different input types depending on parameter type
-rw-r--r--client/js/app/src/app/pages/querybuilder/context/parameters.jsx285
-rw-r--r--client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx10
-rw-r--r--client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx130
3 files changed, 204 insertions, 221 deletions
diff --git a/client/js/app/src/app/pages/querybuilder/context/parameters.jsx b/client/js/app/src/app/pages/querybuilder/context/parameters.jsx
index 9803d824d53..377d60bfcd7 100644
--- a/client/js/app/src/app/pages/querybuilder/context/parameters.jsx
+++ b/client/js/app/src/app/pages/querybuilder/context/parameters.jsx
@@ -1,200 +1,125 @@
+function param(name, type, props = {}) {
+ let children;
+ if (Array.isArray(type)) {
+ children = Object.fromEntries(type.map((child) => [child.name, child]));
+ type = 'Parent';
+ }
+ return Object.assign({ name, type }, children && { children }, props);
+}
+
// https://docs.vespa.ai/en/reference/query-api-reference.html
-export default {
- yql: { name: 'yql', type: 'String' },
+export default param('root', [
+ param('yql', 'String'),
// Native Execution Parameters
- hits: { name: 'hits', type: 'Integer' },
- offset: { name: 'offset', type: 'Integer' },
- queryProfile: { name: 'queryProfile', type: 'String' },
- groupingSessionCache: { name: 'groupingSessionCache', type: 'Boolean' },
- searchChain: { name: 'searchChain', type: 'String' },
- timeout: { name: 'timeout', type: 'Double' },
- noCache: { name: 'noCache', type: 'Boolean' },
+ param('hits', 'Integer', { min: 0, default: 10 }),
+ param('offset', 'Integer', { min: 0, default: 0 }),
+ param('queryProfile', 'String', { default: 'default' }),
+ param('groupingSessionCache', 'Boolean', { default: true }),
+ param('searchChain', 'String', { default: 'default' }),
+ param('timeout', 'Float', { min: 0, default: 0.5 }),
+ param('noCache', 'Boolean', { default: false }),
// Query Model
- model: {
- name: 'model',
- type: 'Parent',
- children: {
- defaultIndex: { name: 'defaultIndex', type: 'String' },
- encoding: { name: 'encoding', type: 'String' },
- filter: { name: 'filter', type: 'String' },
- locale: { name: 'locale', type: 'String' },
- language: { name: 'language', type: 'String' },
- queryString: { name: 'queryString', type: 'String' },
- restrict: { name: 'restrict', type: 'String' },
- searchPath: { name: 'searchPath', type: 'String' },
- sources: { name: 'sources', type: 'String' },
- type: { name: 'type', type: 'String' },
- },
- },
+ param('model', [
+ param('defaultIndex', 'String', { default: 'default' }),
+ param('encoding', 'String', { default: 'utf-8' }),
+ param('filter', 'String'),
+ param('locale', 'String'),
+ param('language', 'String'),
+ param('queryString', 'String'),
+ param('restrict', 'String'),
+ param('searchPath', 'String'),
+ param('sources', 'String'),
+ param('type', 'String'),
+ ]),
// Ranking
- ranking: {
- name: 'ranking',
- type: 'Parent',
- children: {
- location: { name: 'location', type: 'String' },
- features: { name: 'features', type: 'Parent', children: 'String' },
- listFeatures: { name: 'listFeatures', type: 'Boolean' },
- profile: { name: 'profile', type: 'String' },
- properties: { name: 'properties', type: 'String' },
- sorting: { name: 'sorting', type: 'String' },
- freshness: { name: 'freshness', type: 'String' },
- queryCache: { name: 'queryCache', type: 'Boolean' },
- rerankCount: { name: 'rerankCount', type: 'Integer' },
- matching: {
- name: 'matching',
- type: 'Parent',
- children: {
- numThreadsPerSearch: { name: 'numThreadsPerSearch', type: 'Integer' },
- minHitsPerThread: { name: 'minHitsPerThread', type: 'Integer' },
- numSearchPartitions: { name: 'numSearchPartitions', type: 'Integer' },
- termwiseLimit: { name: 'termwiseLimit', type: 'Float' },
- postFilterThreshold: { name: 'postFilterThreshold', type: 'Float' },
- approximateThreshold: {
- name: 'approximateThreshold',
- type: 'Float',
- },
- },
- },
- matchPhase: {
- name: 'matchPhase',
- type: 'Parent',
- children: {
- attribute: { name: 'attribute', type: 'String' },
- maxHits: { name: 'maxHits', type: 'Integer' },
- ascending: { name: 'ascending', type: 'Boolean' },
- diversity: {
- name: 'diversity',
- type: 'Parent',
- children: {
- attribute: { name: 'attribute', type: 'String' },
- minGroups: { name: 'minGroups', type: 'Integer' },
- },
- },
- },
- },
- },
- },
+ param('ranking', [
+ param('location', 'String'),
+ param('features', 'Parent', { children: 'String' }),
+ param('listFeatures', 'Boolean', { default: false }),
+ param('profile', 'String', { default: 'default' }),
+ param('properties', 'String', { children: 'String' }),
+ param('softtimeout', [
+ param('enable', 'Boolean', { default: true }),
+ param('factor', 'Float', { min: 0, max: 1, default: 0.7 }),
+ ]),
+ param('sorting', 'String'),
+ param('freshness', 'String'),
+ param('queryCache', 'Boolean', { default: false }),
+ param('rerankCount', 'Integer', { min: 0 }),
+ param('matching', [
+ param('numThreadsPerSearch', 'Integer', { min: 0 }),
+ param('minHitsPerThread', 'Integer', { min: 0 }),
+ param('numSearchPartitions', 'Integer', { min: 0 }),
+ param('termwiseLimit', 'Float', { min: 0, max: 1 }),
+ param('postFilterThreshold', 'Float', { min: 0, max: 1 }),
+ param('approximateThreshold', 'Float', { min: 0, max: 1 }),
+ ]),
+ param('matchPhase', [
+ param('attribute', 'String'),
+ param('maxHits', 'Integer', { min: 0 }),
+ param('ascending', 'Boolean'),
+ param('diversity', [
+ param('attribute', 'String'),
+ param('minGroups', 'Integer', { min: 0 }),
+ ]),
+ ]),
+ ]),
// Grouping
- collapsesize: { name: 'collapsesize', type: 'Integer' },
- collapsefield: { name: 'collapsefield', type: 'String' },
- collapse: {
- name: 'collapse',
- type: 'Parent',
- children: {
- summary: { name: 'summary', type: 'String' },
- },
- },
- grouping: {
- name: 'grouping',
- type: 'Parent',
- children: {
- defaultMaxGroups: { name: 'defaultMaxGroups', type: 'Integer' },
- defaultMaxHits: { name: 'defaultMaxHits', type: 'Integer' },
- globalMaxGroups: { name: 'globalMaxGroups', type: 'Integer' },
- defaultPrecisionFactor: {
- name: 'defaultPrecisionFactor',
- type: 'Float',
- },
- },
- },
+ param('collapsesize', 'Integer', { min: 1, default: 1 }),
+ param('collapsefield', 'String'),
+ param('collapse', [param('summary', 'String')]),
+ param('grouping', [
+ param('defaultMaxGroups', 'Integer', { min: -1, default: 10 }),
+ param('defaultMaxHits', 'Integer', { min: -1, default: 10 }),
+ param('globalMaxGroups', 'Integer', { min: -1, default: 10000 }),
+ param('defaultPrecisionFactor', 'Float', { min: 0, default: 2.0 }),
+ ]),
// Presentation
- presentation: {
- name: 'presentation',
- type: 'Parent',
- children: {
- bolding: { name: 'bolding', type: 'Boolean' },
- format: {
- name: 'format',
- type: 'Parent',
- children: {
- tensors: { name: 'tensors', type: 'String' },
- },
- },
- template: { name: 'template', type: 'String' },
- summary: { name: 'summary', type: 'String' },
- timing: { name: 'timing', type: 'Boolean' },
- },
- },
+ param('presentation', [
+ param('bolding', 'Boolean', { default: true }),
+ param('format', 'String', { default: 'default' }),
+ param('template', 'String'),
+ param('summary', 'String'),
+ param('timing', 'Boolean', { default: false }),
+ ]),
// Tracing
- trace: {
- name: 'trace',
- type: 'Parent',
- children: {
- level: { name: 'level', type: 'Integer' },
- explainLevel: { name: 'explainLevel', type: 'Integer' },
- profileDepth: { name: 'profileDepth', type: 'Integer' },
- timestamps: { name: 'timestamps', type: 'Boolean' },
- query: { name: 'query', type: 'Boolean' },
- },
- },
+ param('trace', [
+ param('level', 'Integer', { min: 1 }),
+ param('explainLevel', 'Integer', { min: 1 }),
+ param('profileDepth', 'Integer', { min: 1 }),
+ param('timestamps', 'Boolean', { default: false }),
+ param('query', 'Boolean', { default: true }),
+ ]),
// Semantic Rules
- rules: {
- name: 'rules',
- type: 'Parent',
- children: {
- off: { name: 'off', type: 'Boolean' },
- rulebase: { name: 'rulebase', type: 'String' },
- },
- },
- tracelevel: {
- name: 'tracelevel',
- type: 'Parent',
- children: {
- rules: { name: 'rules', type: 'Integer' },
- },
- },
+ param('rules', [
+ param('off', 'Boolean', { default: true }),
+ param('rulebase', 'String'),
+ ]),
+ param('tracelevel', [param('rules', 'Integer', { min: 0 })]),
// Dispatch
- dispatch: {
- name: 'dispatch',
- type: 'Parent',
- children: {
- topKProbability: { name: 'topKProbability', type: 'Float' },
- },
- },
+ param('dispatch', [param('topKProbability', 'Float', { min: 0, max: 1 })]),
// Other
- recall: { name: 'recall', type: 'String' },
- user: { name: 'user', type: 'String' },
- hitcountestimate: { name: 'hitcountestimate', type: 'Boolean' },
- metrics: {
- name: 'metrics',
- type: 'Parent',
- children: {
- ignore: { name: 'ignore', type: 'Boolean' },
- },
- },
- weakAnd: {
- name: 'weakAnd',
- type: 'Parent',
- children: {
- replace: { name: 'replace', type: 'Boolean' },
- },
- },
- wand: {
- name: 'wand',
- type: 'Parent',
- children: {
- hits: { name: 'hits', type: 'Integer' },
- },
- },
+ param('recall', 'String'),
+ param('user', 'String'),
+ param('hitcountestimate', 'Boolean', { default: false }),
+ param('metrics', [param('ignore', 'Boolean', { default: false })]),
+ param('weakAnd', [param('replace', 'Boolean', { default: false })]),
+ param('wand', [param('hits', 'Integer', { default: 100 })]),
- streaming: {
- name: 'streaming',
- type: 'Parent',
- children: {
- userid: { name: 'userid', type: 'Integer' },
- groupname: { name: 'groupname', type: 'String' },
- selection: { name: 'selection', type: 'String' },
- priority: { name: 'priority', type: 'String' },
- maxbucketspervisitor: { name: 'maxbucketspervisitor', type: 'Integer' },
- },
- },
-};
+ param('streaming', [
+ param('userid', 'Integer'),
+ param('groupname', 'String'),
+ param('selection', 'String'),
+ param('priority', 'String'),
+ param('maxbucketspervisitor', 'Integer'),
+ ]),
+]).children;
diff --git a/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx b/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
index df9b8a105a0..23c4af165f0 100644
--- a/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
+++ b/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
@@ -106,7 +106,11 @@ function inputAdd(params, { id: parentId, type: typeName }) {
? { name: typeName, type: parent.type.children }
: parent.type.children[typeName];
- parent.value.push({ id, value: type.children ? [] : '', type });
+ parent.value.push({
+ id,
+ value: type.children ? [] : type.default?.toString() ?? '',
+ type,
+ });
return cloned;
}
@@ -120,8 +124,8 @@ function inputUpdate(params, { id, type, value }) {
typeof parent.type.children === 'string'
? { name: type, type: parent.type.children }
: parent.type.children[type];
- if ((node.type.children != null) !== (newType.children != null))
- node.value = newType.children ? [] : '';
+ if (node.type.type !== newType.type.type)
+ node.value = newType.children ? [] : newType.default?.toString() ?? '';
node.type = newType;
}
if (value != null) node.value = value;
diff --git a/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx b/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
index 668164b54ee..45ebb3bab70 100644
--- a/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
+++ b/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
@@ -6,6 +6,7 @@ import {
Button,
Box,
Stack,
+ Switch,
Badge,
Group,
} from '@mantine/core';
@@ -25,47 +26,100 @@ function AddProperty(props) {
);
}
+function Property({ id, type, types }) {
+ if (types)
+ return (
+ <Select
+ sx={{ flex: 1 }}
+ data={Object.values({ [type.name]: type, ...types }).map(
+ ({ name }) => name
+ )}
+ onChange={(type) => dispatch(ACTION.INPUT_UPDATE, { id, type })}
+ value={type.name}
+ searchable
+ />
+ );
+
+ return (
+ <TextInput
+ sx={{ flex: 1 }}
+ onChange={(event) =>
+ dispatch(ACTION.INPUT_UPDATE, {
+ id,
+ type: event.currentTarget.value,
+ })
+ }
+ placeholder="String"
+ value={type.name}
+ />
+ );
+}
+
+function Value({ id, type, value }) {
+ if (type.children) return null;
+
+ if (type.type === 'Boolean')
+ return (
+ <Switch
+ sx={{ flex: 1 }}
+ onLabel="true"
+ offLabel="false"
+ size="xl"
+ checked={value === 'true'}
+ onChange={(event) =>
+ dispatch(ACTION.INPUT_UPDATE, {
+ id,
+ value: event.currentTarget.checked.toString(),
+ })
+ }
+ />
+ );
+
+ const props = { value, placeholder: type.type };
+ if (type.type === 'Integer' || type.type === 'Float') {
+ props.type = 'number';
+ let range;
+ if (type.min != null) {
+ props.min = type.min;
+ range = `[${props.min}, `;
+ } else range = '(-∞, ';
+ if (type.max != null) {
+ props.max = type.max;
+ range += props.max + ']';
+ } else range += '∞)';
+ props.placeholder += ` in ${range}`;
+
+ if (type.type === 'Float' && type.min != null && type.max != null)
+ props.step = (type.max - type.min) / 100;
+
+ if (parseFloat(value) < type.min || parseFloat(value) > type.max)
+ props.error = `Must be within ${range}`;
+ }
+
+ return (
+ <TextInput
+ sx={{ flex: 1 }}
+ onChange={(event) =>
+ dispatch(ACTION.INPUT_UPDATE, {
+ id,
+ value: event.currentTarget.value,
+ })
+ }
+ {...props}
+ />
+ );
+}
+
function Input({ id, value, types, type }) {
return (
<>
- <Box sx={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
- {types ? (
- <Select
- sx={{ flex: 1 }}
- data={Object.values({ [type.name]: type, ...types }).map(
- ({ name }) => name
- )}
- onChange={(type) => dispatch(ACTION.INPUT_UPDATE, { id, type })}
- value={type.name}
- searchable
- />
- ) : (
- <TextInput
- sx={{ flex: 1 }}
- onChange={(event) =>
- dispatch(ACTION.INPUT_UPDATE, {
- id,
- type: event.currentTarget.value,
- })
- }
- placeholder="String"
- value={type.name}
- />
- )}
- {!type.children && (
- <TextInput
- sx={{ flex: 1 }}
- onChange={(event) =>
- dispatch(ACTION.INPUT_UPDATE, {
- id,
- value: event.currentTarget.value,
- })
- }
- placeholder={type.type}
- value={value}
- />
- )}
- <ActionIcon onClick={() => dispatch(ACTION.INPUT_REMOVE, id)}>
+ <Box sx={{ display: 'flex', gap: '5px' }}>
+ <Property {...{ id, type, types }} />
+ <Value {...{ id, type, value }} />
+ <ActionIcon
+ sx={{ marginTop: 5 }}
+ onClick={() => dispatch(ACTION.INPUT_REMOVE, id)}
+ >
<Icon name="circle-minus" />
</ActionIcon>
</Box>