diff --git a/web/locales/en/plugin__distributed-tracing-console-plugin.json b/web/locales/en/plugin__distributed-tracing-console-plugin.json index 47ee308..e9584cc 100644 --- a/web/locales/en/plugin__distributed-tracing-console-plugin.json +++ b/web/locales/en/plugin__distributed-tracing-console-plugin.json @@ -16,15 +16,18 @@ "TempoStack and TempoMonolithic instances with multi-tenancy are supported. Instances without multi-tenancy are not supported.": "TempoStack and TempoMonolithic instances with multi-tenancy are supported. Instances without multi-tenancy are not supported.", "Tenant": "Tenant", "Select a tenant": "Select a tenant", - "Trace": "Trace", - "Tracing": "Tracing", "Traces": "Traces", "Trace details": "Trace details", + "Trace": "Trace", + "Tracing": "Tracing", "Limit traces": "Limit traces", "No datapoints found.": "No datapoints found.", "Hide graph": "Hide graph", "Show graph": "Show graph", "Filter": "Filter", + "Namespace": "Namespace", + "Filter by namespace": "Filter by namespace", + "This filter is based on the <1>k8s.namespace.name resource attribute. To set this attribute, it is recommended to enable the <4>Kubernetes Attributes Processor in your OpenTelemetry Collector pipeline.": "This filter is based on the <1>k8s.namespace.name resource attribute. To set this attribute, it is recommended to enable the <4>Kubernetes Attributes Processor in your OpenTelemetry Collector pipeline.", "between {{min}} and {{max}}": "between {{min}} and {{max}}", "greater than {{min}}": "greater than {{min}}", "less than {{max}}": "less than {{max}}", diff --git a/web/package-lock.json b/web/package-lock.json index e5c32b4..87e5153 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "distributed-tracing-console-plugin", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "distributed-tracing-console-plugin", - "version": "0.0.1", + "version": "1.0.0", "license": "Apache-2.0", "dependencies": { "@emotion/react": "^11.11.4", @@ -8517,31 +8517,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=18.17" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, "node_modules/cheerio-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", @@ -10378,19 +10353,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -12989,25 +12951,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -13230,6 +13173,27 @@ "yarn": ">=1" } }, + "node_modules/i18next-parser/node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, "node_modules/i18next-parser/node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -13253,6 +13217,25 @@ "node": ">=14.14" } }, + "node_modules/i18next-parser/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/i18next-parser/node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -18929,18 +18912,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/parse5/node_modules/entities": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", @@ -25394,15 +25365,6 @@ "node": "*" } }, - "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -26765,18 +26727,6 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-fetch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", @@ -26784,15 +26734,6 @@ "dev": true, "license": "MIT" }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/web/package.json b/web/package.json index add32f5..dd06089 100644 --- a/web/package.json +++ b/web/package.json @@ -106,6 +106,7 @@ "@perses-dev/dashboards": "0.51.0-rc.0", "@perses-dev/plugin-system": "0.51.0-rc.0", "@tanstack/react-query": "4.36.1", + "cheerio":"1.0.0-rc.12", "react-router-dom": "5.3.4" } } diff --git a/web/src/pages/TracesPage/Toolbar/AttributeFilters.tsx b/web/src/pages/TracesPage/Toolbar/AttributeFilters.tsx index 56e97f3..150a347 100644 --- a/web/src/pages/TracesPage/Toolbar/AttributeFilters.tsx +++ b/web/src/pages/TracesPage/Toolbar/AttributeFilters.tsx @@ -8,7 +8,7 @@ import { FormGroup, } from '@patternfly/react-core'; import { FilterIcon, HelpIcon } from '@patternfly/react-icons'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { TempoInstance } from '../../../hooks/useTempoInstance'; import { ControlledSimpleSelect } from '../../../components/ControlledSelects'; import { TypeaheadSelectOption } from '@patternfly/react-templates'; @@ -21,9 +21,13 @@ import { traceQLToFilter } from './Filter/traceql_to_filter'; import { TypeaheadCheckboxSelect } from '../../../components/TypeaheadCheckboxSelect'; import { DurationField, Filter, splitByUnquotedWhitespace } from './Filter/filter'; import { useTagValues } from '../../../hooks/useTagValues'; +import { Link } from 'react-router-dom-v5-compat'; import { useTimeRange } from '@perses-dev/plugin-system'; import { isAbsoluteTimeRange, toAbsoluteTimeRange } from '@perses-dev/core'; +const k8sAttributesProcessorLink = + 'https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/red_hat_build_of_opentelemetry/configuring-the-collector#kubernetes-attributes-processor_otel-collector-processors'; + const serviceNameFilter = { content: 'Service Name', value: 'serviceName' }; const spanNameFilter = { content: 'Span Name', value: 'spanName' }; const namespaceFilter = { content: 'Namespace', value: 'namespace' }; @@ -111,47 +115,65 @@ export function AttributeFilters(props: AttributeFiltersProps) { setFilter({ ...filter, serviceName: x })} /> setFilter({ ...filter, spanName: x })} /> {t('Filter by namespace')}} + bodyContent={ +
+ + This filter is based on the k8s.namespace.name resource attribute. To + set this attribute, it is recommended to enable the{' '} + Kubernetes Attributes Processor in + your OpenTelemetry Collector pipeline. + +
+ } + > + + + } show={activeFilter === namespaceFilter.value} options={namespaceOptions ?? []} value={filter.namespace} setValue={(x) => setFilter({ ...filter, namespace: x })} /> setFilter({ ...filter, status: x })} /> setFilter({ ...filter, spanDuration: value })} /> setFilter({ ...filter, traceDuration: value })} /> setFilter({ ...filter, customMatchers: value })} @@ -161,7 +183,9 @@ export function AttributeFilters(props: AttributeFiltersProps) { } interface TypeaheadStringAttributeFilterProps { - label: string; + filterName: string; + label?: React.ReactNode; + labelHelp?: React.ReactElement; show?: boolean; options: TypeaheadSelectOption[]; value: string[]; @@ -169,22 +193,24 @@ interface TypeaheadStringAttributeFilterProps { } function TypeaheadStringAttributeFilter(props: TypeaheadStringAttributeFilterProps) { - const { label, show, options, value, setValue } = props; + const { filterName, label, labelHelp, show, options, value, setValue } = props; return ( setValue(value.filter((x) => x !== label))} deleteLabelGroup={() => setValue([])} - categoryName={label} + categoryName={filterName} showToolbarItem={show} >
-  }> +  } labelHelp={labelHelp}> 0 ? ' (' + value.length + ')' : ''}`} + placeholder={`Filter by ${filterName}${ + value.length > 0 ? ' (' + value.length + ')' : '' + }`} options={options} value={value} setValue={setValue} @@ -197,7 +223,7 @@ function TypeaheadStringAttributeFilter(props: TypeaheadStringAttributeFilterPro } interface DurationAttributeFilterProps { - label: string; + filterName: string; show?: boolean; value: DurationField; setValue: (value: DurationField) => void; @@ -205,7 +231,7 @@ interface DurationAttributeFilterProps { function DurationAttributeFilter(props: DurationAttributeFilterProps) { const { t } = useTranslation('plugin__distributed-tracing-console-plugin'); - const { label, show, value, setValue } = props; + const { filterName, show, value, setValue } = props; const { min, max } = value; let labels: string[] = []; @@ -222,7 +248,7 @@ function DurationAttributeFilter(props: DurationAttributeFilterProps) { labels={labels} deleteLabel={() => setValue({ min: undefined, max: undefined })} deleteLabelGroup={() => setValue({ min: undefined, max: undefined })} - categoryName={label} + categoryName={filterName} showToolbarItem={show} > @@ -274,14 +300,14 @@ export function DurationTextInput(props: DurationTextInputProps) { } interface CustomAttributesFilterProps { - label: string; + filterName: string; show?: boolean; value: string[]; setValue: (value: string[]) => void; } function CustomAttributesFilter(props: CustomAttributesFilterProps) { - const { label, show, value, setValue } = props; + const { filterName, show, value, setValue } = props; const { t } = useTranslation('plugin__distributed-tracing-console-plugin'); return ( @@ -289,7 +315,7 @@ function CustomAttributesFilter(props: CustomAttributesFilterProps) { labels={value} deleteLabel={(_category, label) => setValue(value.filter((x) => x !== label))} deleteLabelGroup={() => setValue([])} - categoryName={label} + categoryName={filterName} showToolbarItem={show} > @@ -303,10 +329,8 @@ function CustomAttributesFilter(props: CustomAttributesFilterProps) {
{t( 'Attributes are written in the form key=value and are combined via AND. Multiple attributes can be separated via space. String values must be quoted. Example:', - )} -
-                    {'span.http.status_code=200 span.http.method="GET" duration>5s'}
-                  
+ )}{' '} + {'span.http.status_code=200 span.http.method="GET" duration>5s'}
} >