Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ packages/kbn-managed-vscode-config @elastic/kibana-operations
packages/kbn-managed-vscode-config-cli @elastic/kibana-operations
packages/kbn-management/cards_navigation @elastic/platform-deployment-management
src/plugins/management @elastic/platform-deployment-management
packages/kbn-management/settings/components/field_category @elastic/platform-deployment-management
packages/kbn-management/settings/components/field_input @elastic/platform-deployment-management
packages/kbn-management/settings/components/field_row @elastic/platform-deployment-management
packages/kbn-management/settings/components/form @elastic/platform-deployment-management
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@
"@kbn/logstash-plugin": "link:x-pack/plugins/logstash",
"@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation",
"@kbn/management-plugin": "link:src/plugins/management",
"@kbn/management-settings-components-field-category": "link:packages/kbn-management/settings/components/field_category",
"@kbn/management-settings-components-field-input": "link:packages/kbn-management/settings/components/field_input",
"@kbn/management-settings-components-field-row": "link:packages/kbn-management/settings/components/field_row",
"@kbn/management-settings-components-form": "link:packages/kbn-management/settings/components/form",
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-management/settings/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ These packages comprise the Management Advanced Settings application. The sourc

## Notes

**Be aware**: the functional flow logic we've adopted for these components is not one I would encourage, specifically, using "drilled" onChange handlers and utilizing a composing-component-based store. Ideally, we'd use a Redux store, or, at the very least, a React reducer.
**Be aware**: the functional flow logic we've adopted for these components is not one I would encourage, specifically, using "drilled" onFieldChange handlers and utilizing a composing-component-based store. Ideally, we'd use a Redux store, or, at the very least, a React reducer.

In the interest of time and compatibility, we've opted to use the pattern from the original components in `advancedSettings`. We plan to revisit the state management and prop-drilling when `advancedSettings` is refactored with these components.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: management/settings/components/fieldCategory
slug: /management/settings/components/field-category
title: Management Settings Field Category Component
description: A package containing components for rendering field rows in collections organized by their category.
tags: ['management', 'settings']
date: 2023-10-25
---

## Description

This package contains a component for rendering field rows of `UiSetting` objects in collections organized by their category. It's used primarily by the `Form` component.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import type { ComponentMeta, Story } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { FieldCategories as Component } from '../categories';
import { Params, useCategoryStory } from './use_category_story';
import { FieldCategoryProvider } from '../services';

export default {
title: 'Settings/Field Category/Categories',
description: '',
args: {
isFiltered: false,
isSavingEnabled: true,
},
argTypes: {
isFiltered: {
control: {
type: 'boolean',
},
},
isSavingEnabled: {
control: {
type: 'boolean',
},
},
},
parameters: {
backgrounds: {
default: 'ghost',
},
},
} as ComponentMeta<typeof Component>;

export const Categories: Story<Params> = (params) => {
const { onClearQuery, isSavingEnabled, onFieldChange, unsavedChanges, categorizedFields } =
useCategoryStory(params);

return (
<FieldCategoryProvider
showDanger={action('showDanger')}
links={{ deprecationKey: 'link/to/deprecation/docs' }}
>
<Component
{...{ categorizedFields, onFieldChange, unsavedChanges, onClearQuery, isSavingEnabled }}
/>
</FieldCategoryProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import type { ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';

import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';
import { getFieldDefinitions } from '@kbn/management-settings-field-definition';
import { categorizeFields } from '@kbn/management-settings-utilities';
import { FieldRow } from '@kbn/management-settings-components-field-row';

import { FieldCategory as Component, type FieldCategoryProps as ComponentProps } from '../category';
import { Params, useCategoryStory } from './use_category_story';
import { FieldCategoryProvider } from '../services';

const settings = getSettingsMock();

// Markdown and JSON fields require Monaco, which are *notoriously* slow in Storybook due
// to the lack of a webworker. Until we can resolve it, filter out those fields.
const definitions = getFieldDefinitions(settings, {
isCustom: () => {
return false;
},
isOverridden: () => {
return false;
},
}).filter((field) => field.type !== 'json' && field.type !== 'markdown');

const categories = Object.keys(categorizeFields(definitions));

export default {
title: 'Settings/Field Category/Category',
description: '',
args: {
category: categories[0],
isFiltered: false,
isSavingEnabled: true,
},
argTypes: {
category: {
control: {
type: 'select',
options: categories,
},
},
},
} as ComponentMeta<typeof Component>;

type FieldCategoryParams = Pick<ComponentProps, 'category'> & Params;

export const Category = ({ isFiltered, category, isSavingEnabled }: FieldCategoryParams) => {
const { onClearQuery, onFieldChange, unsavedChanges } = useCategoryStory({
isFiltered,
isSavingEnabled,
});

const { count, fields } = categorizeFields(definitions)[category];
const rows = isFiltered ? [fields[0]] : fields;

return (
<FieldCategoryProvider
showDanger={action('showDanger')}
links={{ deprecationKey: 'link/to/deprecation/docs' }}
{...{ isSavingEnabled, onFieldChange }}
>
<Component category={category} fieldCount={count} onClearQuery={onClearQuery}>
{rows.map((field) => (
<FieldRow
key={field.id}
unsavedChange={unsavedChanges[field.id]}
{...{ field, isSavingEnabled, onFieldChange }}
/>
))}
</Component>
</FieldCategoryProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { useArgs } from '@storybook/client-api';
import { action } from '@storybook/addon-actions';

import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';
import { getFieldDefinitions } from '@kbn/management-settings-field-definition';
import { categorizeFields } from '@kbn/management-settings-utilities';
import { UnsavedFieldChanges, OnFieldChangeFn } from '@kbn/management-settings-types';

export interface Params {
isFiltered: boolean;
isSavingEnabled: boolean;
}

export const useCategoryStory = ({ isFiltered, isSavingEnabled }: Params) => {
const [_args, updateArgs] = useArgs();
const settings = getSettingsMock();

// Markdown and JSON fields require Monaco, which are *notoriously* slow in Storybook due
// to the lack of a webworker. Until we can resolve it, filter out those fields.
const definitions = getFieldDefinitions(settings, {
isCustom: () => {
return false;
},
isOverridden: () => {
return false;
},
}).filter((field) => field.type !== 'json' && field.type !== 'markdown');

const categorizedFields = categorizeFields(definitions);

if (isFiltered) {
Object.keys(categorizedFields).forEach((category) => {
categorizedFields[category].fields = categorizedFields[category].fields.slice(0, 1);
});
}

const onClearQuery = () => updateArgs({ isFiltered: false });

const [unsavedChanges, setUnsavedChanges] = React.useState<UnsavedFieldChanges>({});

const onFieldChange: OnFieldChangeFn = (id, change) => {
action('onFieldChange')(id, change);

if (!change) {
const { [id]: unsavedChange, ...rest } = unsavedChanges;
setUnsavedChanges(rest);
return;
}

setUnsavedChanges((changes) => ({ ...changes, [id]: change }));
};

return { onClearQuery, onFieldChange, isSavingEnabled, unsavedChanges, categorizedFields };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';

import { CategorizedFields, UnsavedFieldChanges } from '@kbn/management-settings-types';

import { FieldRow, FieldRowProps } from '@kbn/management-settings-components-field-row';
import { FieldCategory, type FieldCategoryProps } from './category';

/**
* Props for the {@link FieldCategories} component.
*/
export interface FieldCategoriesProps
extends Pick<FieldCategoryProps, 'onClearQuery'>,
Pick<FieldRowProps, 'onFieldChange' | 'isSavingEnabled'> {
/** Categorized fields for display. */
categorizedFields: CategorizedFields;
/** And unsaved changes currently managed by the parent component. */
unsavedChanges?: UnsavedFieldChanges;
}

/**
* Convenience component for displaying a set of {@link FieldCategory} components, given
* a set of categorized fields.
*
* @param {FieldCategoriesProps} props props to pass to the {@link FieldCategories} component.
*/
export const FieldCategories = ({
categorizedFields,
unsavedChanges = {},
onClearQuery,
isSavingEnabled,
onFieldChange,
}: FieldCategoriesProps) => (
<>
{Object.entries(categorizedFields).map(([category, { count, fields }]) => (
<FieldCategory key={category} fieldCount={count} {...{ category, onClearQuery }}>
{fields.map((field) => (
<FieldRow
key={field.id}
unsavedChange={unsavedChanges[field.id]}
{...{ field, isSavingEnabled, onFieldChange }}
/>
))}
</FieldCategory>
))}
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { ReactElement, Children } from 'react';

import { EuiFlexGroup, EuiFlexItem, EuiSplitPanel, EuiTitle, useEuiTheme } from '@elastic/eui';

import { getCategoryName } from '@kbn/management-settings-utilities';
import type { FieldRowProps } from '@kbn/management-settings-components-field-row';
import { css } from '@emotion/react';
import { ClearQueryLink, ClearQueryLinkProps } from './clear_query_link';

/**
* Props for a {@link FieldCategory} component.
*/
export interface FieldCategoryProps
extends Pick<ClearQueryLinkProps, 'onClearQuery' | 'fieldCount'> {
/** The name of the category. */
category: string;
/** Children-- should be {@link FieldRow} components. */
children:
| ReactElement<FieldRowProps, 'FieldRow'>
| Array<ReactElement<FieldRowProps, 'FieldRow'>>;
}

/**
* Component for displaying a container of fields pertaining to a single
* category.
* @param props - the props to pass to the {@link FieldCategory} component.
*/
export const FieldCategory = (props: FieldCategoryProps) => {
const { category, fieldCount, onClearQuery, children } = props;
const {
euiTheme: { size },
} = useEuiTheme();

const displayCount = Children.count(children);

const panelCSS = css`
& + & {
margin-top: ${size.l};
}
`;

return (
<EuiSplitPanel.Outer hasBorder key={category} css={panelCSS}>
<EuiSplitPanel.Inner color="subdued">
<EuiFlexGroup alignItems="baseline">
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>{getCategoryName(category)}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<ClearQueryLink {...{ displayCount, fieldCount, onClearQuery }} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiSplitPanel.Inner>
<EuiSplitPanel.Inner>{children}</EuiSplitPanel.Inner>
</EuiSplitPanel.Outer>
);
};
Loading