Skip to content

Commit 5d2ac78

Browse files
whmeJenkins
authored andcommitted
metric-backend: unify "attribute kind" wording across the UI and backend
The resource/scope/data_point concept was named three different ways: the group-by widget called it a "level", the attribute-filter widget an "attribute kind", and the wire schema a "kind". The inconsistency made the code harder to follow and invited mismatches between the widgets. Standardize on the wire schema's name, "attribute kind", throughout, with a single frontend source of truth for its ordering and labels. The wire format is unchanged, so no stored data needs migrating. Change-Id: I733ac20a09f878a1948f3b208d09ca4b6bfc03b7
1 parent fd55716 commit 5d2ac78

24 files changed

Lines changed: 203 additions & 177 deletions

packages/cmk-frontend-vue/src/graph-designer/FormMetricBackendCustomQuery.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,11 @@ const groupByInputType = computed<GroupByInputType>(() =>
198198
groupByInputTypeOf(consolidationFunction.value)
199199
)
200200
201-
// The group-by pills pick from the same attribute keys as the where clause, and a group
202-
// level is an attribute kind, so the resolved kind is the pill's level.
203-
const { querySuggestions: groupByQuerySuggestions, resolveKind: groupByResolveLevel } =
204-
useAttributeKeySuggestions(() => buildAutocompleteContext([], { metricName: metricName.value }))
201+
// The group-by pills pick from the same attribute keys as the where clause.
202+
const {
203+
querySuggestions: groupByQuerySuggestions,
204+
resolveAttributeKind: groupByResolveAttributeKind
205+
} = useAttributeKeySuggestions(() => buildAutocompleteContext([], { metricName: metricName.value }))
205206
</script>
206207

207208
<template>
@@ -250,7 +251,7 @@ const { querySuggestions: groupByQuerySuggestions, resolveKind: groupByResolveLe
250251
v-model="groupBy"
251252
:input-type="groupByInputType"
252253
:query-suggestions="groupByQuerySuggestions"
253-
:resolve-level="groupByResolveLevel"
254+
:resolve-attribute-kind="groupByResolveAttributeKind"
254255
/>
255256
</td>
256257
</tr>

packages/cmk-frontend-vue/src/graphing/designer/components/forms/MetricBackendForm.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,11 @@ function storedGroupBy(): GroupByModel {
277277
278278
watch(groupBy, () => storeCurrentWith({}))
279279
280-
// The group-by pills pick from the same attribute keys as the where clause, and a group
281-
// level is an attribute kind, so the resolved kind is the pill's level.
282-
const { querySuggestions: groupByQuerySuggestions, resolveKind: groupByResolveLevel } =
283-
useAttributeKeySuggestions(() => buildAutocompleteContext([], { metricName: metricName.value }))
280+
// The group-by pills pick from the same attribute keys as the where clause.
281+
const {
282+
querySuggestions: groupByQuerySuggestions,
283+
resolveAttributeKind: groupByResolveAttributeKind
284+
} = useAttributeKeySuggestions(() => buildAutocompleteContext([], { metricName: metricName.value }))
284285
</script>
285286

286287
<template>
@@ -328,7 +329,7 @@ const { querySuggestions: groupByQuerySuggestions, resolveKind: groupByResolveLe
328329
v-model="groupBy"
329330
:input-type="groupByInputType"
330331
:query-suggestions="groupByQuerySuggestions"
331-
:resolve-level="groupByResolveLevel"
332+
:resolve-attribute-kind="groupByResolveAttributeKind"
332333
/>
333334
</td>
334335
</tr>

packages/cmk-frontend-vue/src/metric-backend/FormMetricBackendAttributes.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const validationMessages = ref<string[]>([])
6969
7070
const {
7171
querySuggestions,
72-
resolveKind: resolveAttributeKind,
72+
resolveAttributeKind,
7373
cachedSuggestions,
7474
suggestionRevision,
7575
clearCache: clearSuggestionCache

packages/cmk-frontend-vue/src/metric-backend/attribute-filter/AttributeFilterPill.vue

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
1212
1313
import DropdownClearButton from '../DropdownClearButton.vue'
1414
import InlineEditPill from '../InlineEditPill.vue'
15-
import { ATTRIBUTE_KIND_LABELS, attributeKindPrefix, operatorPhrase, pillLabel } from './pill-label'
15+
import { ATTRIBUTE_KIND_ORDER, attributeKindLabel } from '../attribute-kind'
16+
import { attributeKindPrefix, operatorPhrase, pillLabel } from './pill-label'
1617
import {
1718
EXISTENCE_OPERATORS,
1819
STRING_OPERATORS,
@@ -53,7 +54,7 @@ const emit = defineEmits<{
5354
(e: 'edit'): void
5455
(e: 'done'): void
5556
(e: 'update:key', value: string): void
56-
(e: 'update:attributeKind', value: AttributeKind): void
57+
(e: 'update:attributeKind', value: AttributeKind | null): void
5758
(e: 'update:operator', value: Operator): void
5859
(e: 'update:value', value: string): void
5960
}>()
@@ -159,7 +160,7 @@ const attributeKindInput = computed<string | null>({
159160
get: () => props.condition.attributeKind,
160161
set: (value) => {
161162
const valid =
162-
value !== null && Object.hasOwn(ATTRIBUTE_KIND_LABELS, value)
163+
value !== null && (ATTRIBUTE_KIND_ORDER as string[]).includes(value)
163164
? (value as AttributeKind)
164165
: null
165166
emit('update:attributeKind', valid)
@@ -215,11 +216,10 @@ watch(
215216
216217
const attributeKindOptions = computed(() => ({
217218
type: 'fixed' as const,
218-
suggestions: [
219-
{ name: 'resource', title: _t('Resource') },
220-
{ name: 'scope', title: _t('Scope') },
221-
{ name: 'data_point', title: _t('Data point') }
222-
]
219+
suggestions: ATTRIBUTE_KIND_ORDER.map((attributeKind) => ({
220+
name: attributeKind,
221+
title: attributeKindLabel(attributeKind)
222+
}))
223223
}))
224224
225225
function operatorSuggestion(name: Operator) {

packages/cmk-frontend-vue/src/metric-backend/attribute-filter/FormAttributeFilter.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const props = withDefaults(
3232
querySuggestions: (condition: Condition, query: string) => ReturnType<QuerySuggestionsFn>
3333
queryValueSuggestions: (condition: Condition, query: string) => ReturnType<QuerySuggestionsFn>
3434
suggestionRevision?: number
35-
resolveAttributeKind?: ((key: string) => AttributeKind) | undefined
35+
resolveAttributeKind?: ((key: string) => AttributeKind | null) | undefined
3636
operators?: Operator[] | undefined
3737
allowOr?: boolean
3838
ariaLabel?: string | undefined
@@ -108,7 +108,7 @@ function updateKey(target: Condition, value: string): void {
108108
)
109109
}
110110
111-
function updateAttributeKind(target: Condition, value: AttributeKind): void {
111+
function updateAttributeKind(target: Condition, value: AttributeKind | null): void {
112112
mapConditions((c) => (c.id === target.id ? { ...c, attributeKind: value } : c))
113113
}
114114

packages/cmk-frontend-vue/src/metric-backend/attribute-filter/pill-label.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,10 @@
66
import usei18n from 'cmk-ui-library/lib/i18n'
77
import type { TranslatedString } from 'cmk-ui-library/lib/i18nString'
88

9+
import { attributeKindLabel } from '../attribute-kind'
910
import { operatorTakesValue } from './types'
1011
import type { AttributeCondition, AttributeKind, Operator } from './types'
1112

12-
export const ATTRIBUTE_KIND_LABELS: Record<Exclude<AttributeKind, null>, string> = {
13-
resource: 'Resource',
14-
scope: 'Scope',
15-
data_point: 'Data point'
16-
}
17-
18-
function attributeKindPrefixes(): Record<Exclude<AttributeKind, null>, TranslatedString> {
19-
const { _t } = usei18n()
20-
return {
21-
resource: _t('[Resource]'),
22-
scope: _t('[Scope]'),
23-
data_point: _t('[Data point]')
24-
}
25-
}
26-
2713
function operatorPhrases(): Record<Operator, TranslatedString> {
2814
const { _t } = usei18n()
2915
return {
@@ -42,8 +28,8 @@ function operatorPhrases(): Record<Operator, TranslatedString> {
4228
}
4329
}
4430

45-
export function attributeKindPrefix(attributeKind: AttributeKind): string {
46-
return attributeKind === null ? '' : `${attributeKindPrefixes()[attributeKind]} `
31+
export function attributeKindPrefix(attributeKind: AttributeKind | null): string {
32+
return attributeKind === null ? '' : `[${attributeKindLabel(attributeKind)}] `
4733
}
4834

4935
export function operatorPhrase(operator: Operator): TranslatedString {

packages/cmk-frontend-vue/src/metric-backend/attribute-filter/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
* This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
44
* conditions defined in the file COPYING, which is part of this source code package.
55
*/
6+
import type { AttributeKind } from '../attribute-kind'
67

7-
export type AttributeKind = 'resource' | 'scope' | 'data_point' | null
8+
export type { AttributeKind }
89

910
export const STRING_OPERATORS = [
1011
'eq',
@@ -41,7 +42,7 @@ export function isOperator(value: string): value is Operator {
4142
}
4243

4344
export interface AttributeCondition {
44-
attributeKind: AttributeKind
45+
attributeKind: AttributeKind | null
4546
key: string | null
4647
operator: Operator
4748
value: string
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright (C) 2026 Checkmk GmbH - License: Checkmk Enterprise License
3+
* This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
4+
* conditions defined in the file COPYING, which is part of this source code package.
5+
*/
6+
import type { AttributeKind } from 'cmk-shared-typing/typescript/attribute_filter'
7+
import usei18n from 'cmk-ui-library/lib/i18n'
8+
import type { TranslatedString } from 'cmk-ui-library/lib/i18nString'
9+
10+
// Wire type, re-exported so the whole metric-backend UI shares one name for it.
11+
export type { AttributeKind }
12+
13+
// Dropdown and suggestion-section order, shared by both metric-backend widgets.
14+
export const ATTRIBUTE_KIND_ORDER: AttributeKind[] = ['resource', 'scope', 'data_point']
15+
16+
// Built at call time, not module load, because i18n is not yet set up then.
17+
export function attributeKindLabel(attributeKind: AttributeKind): TranslatedString {
18+
const { _t } = usei18n()
19+
const labels: Record<AttributeKind, TranslatedString> = {
20+
resource: _t('Resource'),
21+
scope: _t('Scope'),
22+
data_point: _t('Data point')
23+
}
24+
return labels[attributeKind]
25+
}

packages/cmk-frontend-vue/src/metric-backend/attributeFilterAdapter.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ import {
2424
// Flat key/value attribute list, as the autocomplete REST context and readonly rendering use it.
2525
type GraphLineQueryAttributes = Array<{ key: string; value: string }>
2626

27-
// Pill kinds match the shared model's kinds verbatim, so a condition's kind crosses unchanged.
28-
export type AttributeKindKey = Exclude<AttributeKind, null>
29-
30-
export const ATTRIBUTE_KIND_ORDER: AttributeKindKey[] = ['resource', 'scope', 'data_point']
31-
3227
type PositiveLeaf =
3328
| SharedAttributeFilterEquals
3429
| SharedAttributeFilterContains
@@ -151,13 +146,13 @@ export function fromAttributeFilter(
151146
.filter((group) => group.conditions.length > 0)
152147
}
153148

154-
export const KEY_IDENTS: Record<AttributeKindKey, string> = {
149+
export const KEY_IDENTS: Record<AttributeKind, string> = {
155150
resource: 'monitored_resource_attributes_keys_backend',
156151
scope: 'monitored_scope_attributes_keys_backend',
157152
data_point: 'monitored_data_point_attributes_keys_backend'
158153
}
159154

160-
export const VALUE_IDENTS: Record<AttributeKindKey, string> = {
155+
export const VALUE_IDENTS: Record<AttributeKind, string> = {
161156
resource: 'monitored_resource_attributes_values_backend',
162157
scope: 'monitored_scope_attributes_values_backend',
163158
data_point: 'monitored_data_point_attributes_values_backend'

packages/cmk-frontend-vue/src/metric-backend/attributeKeySuggestions.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import {
1212
flattenSuggestions
1313
} from 'cmk-ui-library/components/CmkSuggestions'
1414
import { fetchSuggestions } from 'cmk-ui-library/components/FormAutocompleter/autocompleter'
15-
import usei18n, { untranslated } from 'cmk-ui-library/lib/i18n'
16-
import type { TranslatedString } from 'cmk-ui-library/lib/i18nString'
15+
import { untranslated } from 'cmk-ui-library/lib/i18n'
1716
import { type Ref, ref } from 'vue'
1817

19-
import { ATTRIBUTE_KIND_ORDER, KEY_IDENTS } from './attributeFilterAdapter'
20-
import type { AttributeKindKey, AutoCompleteContext } from './attributeFilterAdapter'
18+
import { ATTRIBUTE_KIND_ORDER, type AttributeKind, attributeKindLabel } from './attribute-kind'
19+
import { KEY_IDENTS } from './attributeFilterAdapter'
20+
import type { AutoCompleteContext } from './attributeFilterAdapter'
2121

2222
/**
2323
* Attribute-key autocomplete across the three attribute kinds, sectioned by kind.
@@ -30,16 +30,14 @@ export function useAttributeKeySuggestions(
3030
buildContext: (excludeId?: string) => AutoCompleteContext
3131
): {
3232
querySuggestions: (query: string, excludeId?: string) => Promise<Response>
33-
resolveKind: (key: string) => AttributeKindKey | null
33+
resolveAttributeKind: (key: string) => AttributeKind | null
3434
cachedSuggestions: (
3535
autocompleter: Autocompleter,
3636
query: string
3737
) => Response | ErrorResponse | undefined
3838
suggestionRevision: Ref<number>
3939
clearCache: () => void
4040
} {
41-
const { _t } = usei18n()
42-
4341
const suggestionCache = new Map<string, Response | ErrorResponse>()
4442
const inflightSuggestions = new Set<string>()
4543
const suggestionRevision = ref(0)
@@ -68,17 +66,11 @@ export function useAttributeKeySuggestions(
6866
suggestionCache.clear()
6967
}
7068

71-
const sectionTitles: Record<AttributeKindKey, TranslatedString> = {
72-
resource: _t('Resource'),
73-
scope: _t('Scope'),
74-
data_point: _t('Data point')
75-
}
76-
7769
// A key may be offered under more than one attribute kind, so record the set of
78-
// kinds each suggested key belongs to (see `resolveKind`).
79-
const keyKindCache = new Map<string, Set<AttributeKindKey>>()
70+
// kinds each suggested key belongs to (see `resolveAttributeKind`).
71+
const keyKindCache = new Map<string, Set<AttributeKind>>()
8072

81-
function cacheKeyKind(name: string, attributeKind: AttributeKindKey): void {
73+
function cacheKeyKind(name: string, attributeKind: AttributeKind): void {
8274
const kinds = keyKindCache.get(name)
8375
if (kinds) {
8476
kinds.add(attributeKind)
@@ -111,7 +103,7 @@ export function useAttributeKeySuggestions(
111103
}
112104
}
113105
if (suggestions.length > 0) {
114-
sections.push({ title: sectionTitles[attributeKind], suggestions })
106+
sections.push({ title: attributeKindLabel(attributeKind), suggestions })
115107
}
116108
})
117109
const userEntry: Section[] = query
@@ -120,12 +112,18 @@ export function useAttributeKeySuggestions(
120112
return new Response([...userEntry, ...sections])
121113
}
122114

123-
function resolveKind(key: string): AttributeKindKey | null {
115+
function resolveAttributeKind(key: string): AttributeKind | null {
124116
// A key offered under more than one attribute kind is ambiguous: leave it
125117
// unresolved so the attribute-kind dropdown opens for the user to choose.
126118
const kinds = keyKindCache.get(key)
127119
return kinds?.size === 1 ? [...kinds][0]! : null
128120
}
129121

130-
return { querySuggestions, resolveKind, cachedSuggestions, suggestionRevision, clearCache }
122+
return {
123+
querySuggestions,
124+
resolveAttributeKind,
125+
cachedSuggestions,
126+
suggestionRevision,
127+
clearCache
128+
}
131129
}

0 commit comments

Comments
 (0)