SearchForm.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { css } from '@emotion/css';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import { SelectableValue } from '@grafana/data';
  4. import { fuzzyMatch, InlineField, InlineFieldRow, Input, Select } from '@grafana/ui';
  5. import { notifyApp } from 'app/core/actions';
  6. import { createErrorNotification } from 'app/core/copy/appNotification';
  7. import { dispatch } from 'app/store/store';
  8. import { JaegerDatasource } from '../datasource';
  9. import { JaegerQuery } from '../types';
  10. import { transformToLogfmt } from '../util';
  11. import { AdvancedOptions } from './AdvancedOptions';
  12. type Props = {
  13. datasource: JaegerDatasource;
  14. query: JaegerQuery;
  15. onChange: (value: JaegerQuery) => void;
  16. };
  17. export const ALL_OPERATIONS_KEY = 'All';
  18. const allOperationsOption: SelectableValue<string> = {
  19. label: ALL_OPERATIONS_KEY,
  20. value: undefined,
  21. };
  22. export function SearchForm({ datasource, query, onChange }: Props) {
  23. const [serviceOptions, setServiceOptions] = useState<Array<SelectableValue<string>>>();
  24. const [operationOptions, setOperationOptions] = useState<Array<SelectableValue<string>>>();
  25. const [isLoading, setIsLoading] = useState<{
  26. services: boolean;
  27. operations: boolean;
  28. }>({
  29. services: false,
  30. operations: false,
  31. });
  32. const loadOptions = useCallback(
  33. async (url: string, loaderOfType: string, query = ''): Promise<Array<SelectableValue<string>>> => {
  34. setIsLoading((prevValue) => ({ ...prevValue, [loaderOfType]: true }));
  35. try {
  36. const values: string[] | null = await datasource.metadataRequest(url);
  37. if (!values) {
  38. return [{ label: `No ${loaderOfType} found`, value: `No ${loaderOfType} found` }];
  39. }
  40. const options: SelectableValue[] = values.sort().map((option) => ({
  41. label: option,
  42. value: option,
  43. }));
  44. const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
  45. return filteredOptions;
  46. } catch (error) {
  47. dispatch(notifyApp(createErrorNotification('Error', error)));
  48. return [];
  49. } finally {
  50. setIsLoading((prevValue) => ({ ...prevValue, [loaderOfType]: false }));
  51. }
  52. },
  53. [datasource]
  54. );
  55. useEffect(() => {
  56. const getServices = async () => {
  57. const services = await loadOptions('/api/services', 'services');
  58. setServiceOptions(services);
  59. };
  60. getServices();
  61. }, [datasource, loadOptions]);
  62. useEffect(() => {
  63. const getOperations = async () => {
  64. const operations = await loadOptions(
  65. `/api/services/${encodeURIComponent(query.service!)}/operations`,
  66. 'operations'
  67. );
  68. setOperationOptions([allOperationsOption, ...operations]);
  69. };
  70. if (query.service) {
  71. getOperations();
  72. }
  73. }, [datasource, query.service, loadOptions]);
  74. return (
  75. <div className={css({ maxWidth: '500px' })}>
  76. <InlineFieldRow>
  77. <InlineField label="Service" labelWidth={14} grow>
  78. <Select
  79. inputId="service"
  80. options={serviceOptions}
  81. onOpenMenu={() => loadOptions('/api/services', 'services')}
  82. isLoading={isLoading.services}
  83. value={serviceOptions?.find((v) => v?.value === query.service) || undefined}
  84. onChange={(v) =>
  85. onChange({
  86. ...query,
  87. service: v?.value!,
  88. operation: query.service !== v?.value ? undefined : query.operation,
  89. })
  90. }
  91. menuPlacement="bottom"
  92. isClearable
  93. aria-label={'select-service-name'}
  94. />
  95. </InlineField>
  96. </InlineFieldRow>
  97. <InlineFieldRow>
  98. <InlineField label="Operation" labelWidth={14} grow disabled={!query.service}>
  99. <Select
  100. inputId="operation"
  101. options={operationOptions}
  102. onOpenMenu={() =>
  103. loadOptions(`/api/services/${encodeURIComponent(query.service!)}/operations`, 'operations')
  104. }
  105. isLoading={isLoading.operations}
  106. value={operationOptions?.find((v) => v.value === query.operation) || null}
  107. onChange={(v) =>
  108. onChange({
  109. ...query,
  110. operation: v?.value! || undefined,
  111. })
  112. }
  113. menuPlacement="bottom"
  114. isClearable
  115. aria-label={'select-operation-name'}
  116. />
  117. </InlineField>
  118. </InlineFieldRow>
  119. <InlineFieldRow>
  120. <InlineField label="Tags" labelWidth={14} grow>
  121. <Input
  122. id="tags"
  123. value={transformToLogfmt(query.tags)}
  124. placeholder="http.status_code=200 error=true"
  125. onChange={(v) =>
  126. onChange({
  127. ...query,
  128. tags: v.currentTarget.value,
  129. })
  130. }
  131. />
  132. </InlineField>
  133. </InlineFieldRow>
  134. <AdvancedOptions query={query} onChange={onChange} />
  135. </div>
  136. );
  137. }
  138. export default SearchForm;