From 2929bf9f41cf64b69caec3d7af2b0f76742e9265 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Thu, 17 Feb 2022 20:59:10 +0200 Subject: [PATCH 01/12] feat: add preview configurations sections --- .../ItemRenderer/ItemRenderer.tsx | 1 + .../SectionRenderer/ItemRenderer/styled.tsx | 4 +++ .../SectionRenderer/SectionHeader.tsx | 5 ++- .../SectionRenderer/SectionRenderer.tsx | 2 +- .../molecules/SectionRenderer/styled.tsx | 26 ++++++++------- src/models/navigator.ts | 2 ++ .../HelmChartSectionBlueprint.ts | 33 +++++++++++++++++-- .../RootHelmChartsSectionBlueprint.ts | 4 ++- src/navsections/sectionBlueprintMap.ts | 4 +-- src/navsections/sectionBlueprintMiddleware.ts | 7 ++-- 10 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/components/molecules/SectionRenderer/ItemRenderer/ItemRenderer.tsx b/src/components/molecules/SectionRenderer/ItemRenderer/ItemRenderer.tsx index 211acbcffb..0714767b5a 100644 --- a/src/components/molecules/SectionRenderer/ItemRenderer/ItemRenderer.tsx +++ b/src/components/molecules/SectionRenderer/ItemRenderer/ItemRenderer.tsx @@ -85,6 +85,7 @@ function ItemRenderer(props: ItemRendererProps {itemInstance.isCheckable && (blueprint.customization?.isCheckVisibleOnHover ? itemInstance.isChecked || isHovered : true) && ( diff --git a/src/components/molecules/SectionRenderer/ItemRenderer/styled.tsx b/src/components/molecules/SectionRenderer/ItemRenderer/styled.tsx index 3027de8798..981d1addfc 100644 --- a/src/components/molecules/SectionRenderer/ItemRenderer/styled.tsx +++ b/src/components/molecules/SectionRenderer/ItemRenderer/styled.tsx @@ -15,6 +15,7 @@ type ItemContainerProps = { $indentation: number; $isSectionCheckable: boolean; $hasCustomNameDisplay: boolean; + $lastItemMarginBottom?: number; }; export const ItemContainer = styled.span` @@ -31,6 +32,9 @@ export const ItemContainer = styled.span` ${props => props.hasOnClick && `cursor: pointer;`} ${props => { if (props.isLastItem) { + if (props.$lastItemMarginBottom !== undefined) { + return `margin-bottom: ${props.$lastItemMarginBottom}px;`; + } return `margin-bottom: 12px;`; } }} diff --git a/src/components/molecules/SectionRenderer/SectionHeader.tsx b/src/components/molecules/SectionRenderer/SectionHeader.tsx index c0c34c9827..eb4e735d46 100644 --- a/src/components/molecules/SectionRenderer/SectionHeader.tsx +++ b/src/components/molecules/SectionRenderer/SectionHeader.tsx @@ -85,6 +85,7 @@ function SectionHeader(props: SectionHeaderProps) { hasCustomNameDisplay={Boolean(NameDisplay.Component)} isLastSection={isLastSection} isCollapsed={isCollapsed} + $marginBottom={sectionBlueprint.customization?.sectionMarginBottom} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > @@ -131,7 +132,9 @@ function SectionHeader(props: SectionHeaderProps) { > {name} - {counter && {counter}} + {counter !== undefined && ( + {counter} + )} {NameSuffix.Component && (NameSuffix.options?.isVisibleOnHover ? isHovered : true) && ( diff --git a/src/components/molecules/SectionRenderer/SectionRenderer.tsx b/src/components/molecules/SectionRenderer/SectionRenderer.tsx index 2154f00178..ad7d4bb066 100644 --- a/src/components/molecules/SectionRenderer/SectionRenderer.tsx +++ b/src/components/molecules/SectionRenderer/SectionRenderer.tsx @@ -219,7 +219,7 @@ function SectionRenderer(props: SectionRendererProps) { itemId={itemId} blueprint={itemBlueprint} level={level + 1} - isLastItem={isLastVisibleItemId(itemId) && !childSectionIds} + isLastItem={isLastVisibleItemId(itemId)} isSectionCheckable={Boolean(sectionInstance.checkable)} sectionContainerElementId={sectionBlueprint.containerElementId} options={itemRendererOptions} diff --git a/src/components/molecules/SectionRenderer/styled.tsx b/src/components/molecules/SectionRenderer/styled.tsx index ff1d9d664a..0c38ab5b39 100644 --- a/src/components/molecules/SectionRenderer/styled.tsx +++ b/src/components/molecules/SectionRenderer/styled.tsx @@ -11,6 +11,18 @@ type NameContainerProps = { isCheckable?: boolean; }; +export const NameContainer = styled.span` + display: flex; + align-items: center; + width: 100%; + ${props => { + const defaultIndentation = props.isCheckable ? 24 : 0; + return `padding-left: ${defaultIndentation + props.$indentation}px;`; + }} + ${props => !props.isHovered && 'padding-right: 30px;'} + ${props => props.$hasCustomNameDisplay && 'padding: 0;'} +`; + type SectionContainerProps = { isSelected?: boolean; isHighlighted?: boolean; @@ -23,20 +35,9 @@ type SectionContainerProps = { disableHoverStyle?: boolean; isSectionCheckable?: boolean; hasCustomNameDisplay?: boolean; + $marginBottom?: number; }; -export const NameContainer = styled.span` - display: flex; - align-items: center; - width: 100%; - ${props => { - const defaultIndentation = props.isCheckable ? 24 : 0; - return `padding-left: ${defaultIndentation + props.$indentation}px;`; - }} - ${props => !props.isHovered && 'padding-right: 30px;'} - ${props => props.$hasCustomNameDisplay && 'padding: 0;'} -`; - export const SectionContainer = styled.li` display: flex; justify-content: space-between; @@ -77,6 +78,7 @@ export const SectionContainer = styled.li` return `background: ${Colors.blackPearl};`; } }}; + ${props => props.$marginBottom && `margin-bottom: ${props.$marginBottom}px;`} `; type NameProps = { diff --git a/src/models/navigator.ts b/src/models/navigator.ts index 14ecb509bf..bf2231a166 100644 --- a/src/models/navigator.ts +++ b/src/models/navigator.ts @@ -39,6 +39,7 @@ export interface ItemCustomization { }; disableHoverStyle?: boolean; isCheckVisibleOnHover?: boolean; + lastItemMarginBottom?: number; } export type SectionCustomComponentProps = { @@ -80,6 +81,7 @@ export interface SectionCustomization { disableHoverStyle?: boolean; beforeInitializationText?: string; isCheckVisibleOnHover?: boolean; + sectionMarginBottom?: number; } export interface ItemBlueprint { diff --git a/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts b/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts index cf7411ff81..05cb7855ad 100644 --- a/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts +++ b/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts @@ -28,6 +28,33 @@ type HelmChartScopeType = { }; export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { + // TODO: replace 'any' type after implementing the state for preview configurations + const previewConfigurationsSectionBlueprint: SectionBlueprint = { + name: 'Preview Configurations', + id: `${helmChart.id}-configurations`, + containerElementId: 'helm-section-container', + rootSectionId: HELM_CHART_SECTION_NAME, + getScope: () => { + return {}; + }, + builder: { + isInitialized: () => true, + isVisible: () => true, + }, + customization: { + counterDisplayMode: 'items', + indentation: 10, + nameWeight: 400, + nameSize: 14, + nameColor: Colors.grey9, + nameHorizontalPadding: 0, + namePrefix: { + component: CollapseSectionPrefix, + }, + sectionMarginBottom: 12, + }, + }; + const valuesFilesSectionBlueprint: SectionBlueprint = { name: 'Values Files', id: `${helmChart.id}-values`, @@ -99,6 +126,7 @@ export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { prefix: { component: FileItemPrefix, }, + lastItemMarginBottom: 0, }, }, }; @@ -108,7 +136,7 @@ export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { name: helmChart.name, containerElementId: 'helm-sections-container', rootSectionId: HELM_CHART_SECTION_NAME, - childSectionIds: [valuesFilesSectionBlueprint.id], + childSectionIds: [valuesFilesSectionBlueprint.id, previewConfigurationsSectionBlueprint.id], getScope: state => { const kubeConfigPath = state.config.projectConfig?.kubeConfig?.path || state.config.kubeConfig.path; return { @@ -160,6 +188,7 @@ export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { }, customization: { prefix: {component: FileItemPrefix}, + lastItemMarginBottom: 0, }, }, customization: { @@ -171,5 +200,5 @@ export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { }, }; - return {helmChartSectionBlueprint, valuesFilesSectionBlueprint}; + return {helmChartSectionBlueprint, valuesFilesSectionBlueprint, previewConfigurationsSectionBlueprint}; } diff --git a/src/navsections/HelmChartSectionBlueprint/RootHelmChartsSectionBlueprint.ts b/src/navsections/HelmChartSectionBlueprint/RootHelmChartsSectionBlueprint.ts index 706ac6b5da..e1ea3ce18a 100644 --- a/src/navsections/HelmChartSectionBlueprint/RootHelmChartsSectionBlueprint.ts +++ b/src/navsections/HelmChartSectionBlueprint/RootHelmChartsSectionBlueprint.ts @@ -64,12 +64,14 @@ const RootHelmChartsSectionBlueprint: SectionBlueprint { - const {valuesFilesSectionBlueprint, helmChartSectionBlueprint} = makeHelmChartSectionBlueprint(helmChart); + const {valuesFilesSectionBlueprint, helmChartSectionBlueprint, previewConfigurationsSectionBlueprint} = + makeHelmChartSectionBlueprint(helmChart); if (RootHelmChartsSectionBlueprint.childSectionIds) { RootHelmChartsSectionBlueprint.childSectionIds?.push(helmChartSectionBlueprint.id); } else { RootHelmChartsSectionBlueprint.childSectionIds = [helmChartSectionBlueprint.id]; } + sectionBlueprintMap.register(previewConfigurationsSectionBlueprint); sectionBlueprintMap.register(valuesFilesSectionBlueprint); sectionBlueprintMap.register(helmChartSectionBlueprint); }); diff --git a/src/navsections/sectionBlueprintMap.ts b/src/navsections/sectionBlueprintMap.ts index 9c3212758a..28ab6a8427 100644 --- a/src/navsections/sectionBlueprintMap.ts +++ b/src/navsections/sectionBlueprintMap.ts @@ -1,4 +1,5 @@ import {EventEmitter} from 'events'; +import log from 'loglevel'; import {SectionBlueprint} from '@models/navigator'; @@ -18,8 +19,7 @@ const resizeObserver = new window.ResizeObserver(entries => { const register = (sectionBlueprint: SectionBlueprint) => { if (SectionBlueprintMap[sectionBlueprint.id]) { - // eslint-disable-next-line no-console - console.warn(`Overriding existing sectionBlueprint with id ${sectionBlueprint.id}`); + log.warn(`Overriding existing sectionBlueprint with id ${sectionBlueprint.id}`); } SectionBlueprintMap[sectionBlueprint.id] = sectionBlueprint; eventEmitter.emit('register', sectionBlueprint); diff --git a/src/navsections/sectionBlueprintMiddleware.ts b/src/navsections/sectionBlueprintMiddleware.ts index a833f49cd8..de520c6e11 100644 --- a/src/navsections/sectionBlueprintMiddleware.ts +++ b/src/navsections/sectionBlueprintMiddleware.ts @@ -299,8 +299,11 @@ const processSectionBlueprints = async (state: RootState, dispatch: AppDispatch) Boolean(sectionBuilder?.shouldBeVisibleBeforeInitialized === true && !isSectionInitialized) || (sectionBlueprint && sectionBlueprint.customization?.emptyDisplay && isSectionEmpty) || (isSectionInitialized && - Boolean(sectionBuilder?.isVisible ? sectionBuilder.isVisible(sectionScope, rawItems) : true) && - (visibleItemIds.length > 0 || visibleGroupIds.length > 0)), + Boolean( + sectionBuilder?.isVisible + ? sectionBuilder.isVisible(sectionScope, rawItems) + : visibleItemIds.length > 0 || visibleGroupIds.length > 0 + )), isInitialized: isSectionInitialized, isSelected: isSectionSelected, isHighlighted: isSectionHighlighted, From 8e8bbedf3c16f7232f1624c855d0eb4eb9d46200 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:26:00 +0200 Subject: [PATCH 02/12] feat: add create button for preview configurations --- .../HelmChartSectionBlueprint.ts | 7 +++ .../PreviewConfigurationQuickAction.tsx | 51 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx diff --git a/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts b/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts index 05cb7855ad..1b80feba34 100644 --- a/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts +++ b/src/navsections/HelmChartSectionBlueprint/HelmChartSectionBlueprint.ts @@ -11,6 +11,7 @@ import Colors from '@styles/Colors'; import CollapseSectionPrefix from './CollapseSectionPrefix'; import FileItemPrefix from './FileItemPrefix'; import HelmChartQuickAction from './HelmChartQuickAction'; +import PreviewConfigurationNameSuffix from './PreviewConfigurationQuickAction'; export type ValuesFilesScopeType = { helmValuesMap: HelmValuesMapType; @@ -52,6 +53,12 @@ export function makeHelmChartSectionBlueprint(helmChart: HelmChart) { component: CollapseSectionPrefix, }, sectionMarginBottom: 12, + nameSuffix: { + component: PreviewConfigurationNameSuffix, + options: { + isVisibleOnHover: true, + }, + }, }, }; diff --git a/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx new file mode 100644 index 0000000000..399e18554f --- /dev/null +++ b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import {Button, Tooltip} from 'antd'; + +import {PlusOutlined} from '@ant-design/icons'; + +import styled from 'styled-components'; + +import {SectionCustomComponentProps} from '@models/navigator'; + +import {useAppSelector} from '@redux/hooks'; + +import Colors from '@styles/Colors'; + +const SuffixContainer = styled.span` + display: inline-block; +`; + +const ButtonContainer = styled.span` + display: flex; + align-items: center; + padding: 0 4px; + margin-right: 2px; + & .ant-btn-sm { + height: 20px; + width: 20px; + } +`; + +const PreviewConfigurationNameSuffix: React.FC = props => { + const {sectionInstance} = props; + const isSectionCollapsed = useAppSelector(state => state.navigator.collapsedSectionIds.includes(sectionInstance.id)); + + return ( + + + + - + {entries.map(entry => ( - - - - - {entry.key && ( - - )} - - - removeEntry(entry.id)} - color={Colors.redError} - size="small" - icon={} - /> - + updateEntryKey(entry.id, newKey)} + onValueChange={newValue => updateEntryValue(entry.id, newValue)} + onEntryRemove={removeEntry} + availableKeys={getEntryAvailableKeys(entry)} + availableValues={getEntryAvailableValues(entry)} + /> ))} - + ); } diff --git a/src/components/atoms/KeyValueInput/constants.ts b/src/components/atoms/KeyValueInput/constants.ts new file mode 100644 index 0000000000..e6edb94c08 --- /dev/null +++ b/src/components/atoms/KeyValueInput/constants.ts @@ -0,0 +1 @@ +export const ANY_VALUE = ''; diff --git a/src/components/atoms/KeyValueInput/styled.tsx b/src/components/atoms/KeyValueInput/styled.tsx new file mode 100644 index 0000000000..df52b620e8 --- /dev/null +++ b/src/components/atoms/KeyValueInput/styled.tsx @@ -0,0 +1,34 @@ +import {Button} from 'antd'; + +import styled from 'styled-components'; + +export const Container = styled.div` + max-height: 800px; + overflow-y: auto; +`; + +export const TitleContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; +export const TitleLabel = styled.span``; + +export const KeyValueContainer = styled.div` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-gap: 8px; + align-items: center; + margin: 10px 0; +`; + +export const KeyValueRemoveButtonContainer = styled.div` + display: grid; + grid-template-columns: 1fr max-content; + grid-gap: 8px; + align-items: center; +`; + +export const StyledRemoveButton = styled(Button)` + min-width: 24px; +`; diff --git a/src/components/atoms/KeyValueInput/types.ts b/src/components/atoms/KeyValueInput/types.ts new file mode 100644 index 0000000000..77de45f4f5 --- /dev/null +++ b/src/components/atoms/KeyValueInput/types.ts @@ -0,0 +1,2 @@ +export type KeyValueEntry = {id: string; key?: string; value?: string}; +export type KeyValueData = Record; diff --git a/src/components/molecules/ResourceFilter/ResourceFilter.tsx b/src/components/molecules/ResourceFilter/ResourceFilter.tsx index 224d12f5f9..9c45369491 100644 --- a/src/components/molecules/ResourceFilter/ResourceFilter.tsx +++ b/src/components/molecules/ResourceFilter/ResourceFilter.tsx @@ -3,6 +3,7 @@ import {useDebounce} from 'react-use'; import {Button, Input, Select} from 'antd'; +import {mapValues} from 'lodash'; import styled from 'styled-components'; import {DEFAULT_EDITOR_DEBOUNCE} from '@constants/constants'; @@ -104,13 +105,15 @@ const ResourceFilter = () => { ].sort(); }, [knownResourceKinds, resourceMap]); - const allLabels = useMemo>(() => { + const allLabelsData = useMemo>(() => { return makeKeyValuesFromObjectList(Object.values(resourceMap), resource => resource.content?.metadata?.labels); }, [resourceMap]); + const allLabelsSchema = useMemo(() => mapValues(allLabelsData, () => 'string'), [allLabelsData]); - const allAnnotations = useMemo>(() => { + const allAnnotationsData = useMemo>(() => { return makeKeyValuesFromObjectList(Object.values(resourceMap), resource => resource.content?.metadata?.annotations); }, [resourceMap]); + const allAnnotationsSchema = useMemo(() => mapValues(allAnnotationsData, () => 'string'), [allAnnotationsData]); const fileOrFolderContainedInOptions = useMemo(() => { return Object.keys(fileMap).map(option => ( @@ -275,7 +278,8 @@ const ResourceFilter = () => { { From fa65358f0780f3bb989195b1e8c96652f47d5ce7 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Fri, 18 Feb 2022 13:23:35 +0200 Subject: [PATCH 07/12] refactor: KeyValueInput improvements --- .../KeyValueInput/KeyValueEntryRenderer.tsx | 37 +----------------- .../atoms/KeyValueInput/KeyValueInput.tsx | 9 ++++- .../atoms/KeyValueInput/ValueInput.tsx | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 src/components/atoms/KeyValueInput/ValueInput.tsx diff --git a/src/components/atoms/KeyValueInput/KeyValueEntryRenderer.tsx b/src/components/atoms/KeyValueInput/KeyValueEntryRenderer.tsx index 83c44169ca..f81466386c 100644 --- a/src/components/atoms/KeyValueInput/KeyValueEntryRenderer.tsx +++ b/src/components/atoms/KeyValueInput/KeyValueEntryRenderer.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import {Input, Select} from 'antd'; +import {Select} from 'antd'; import {MinusOutlined} from '@ant-design/icons'; import Colors from '@styles/Colors'; -import {ANY_VALUE} from './constants'; +import ValueInput from './ValueInput'; import {KeyValueEntry} from './types'; import * as S from './styled'; @@ -22,39 +22,6 @@ type KeyValueEntryRendererProps = { availableValues?: string[]; }; -type ValueInputProps = { - value?: string; - valueType: string; - availableValues?: string[]; - onChange: (newValue: string) => void; - disabled?: boolean; -}; - -const ValueInput: React.FC = props => { - const {value, valueType, availableValues, disabled, onChange} = props; - - if (valueType === 'string') { - if (availableValues?.length) { - return ( - - ); - } - return onChange(e.target.value)} disabled={disabled} />; - } - - // TODO: decide if we want to implement more value types - return null; -}; - const KeyValueEntryRenderer: React.FC = props => { const {entry, valueType, onKeyChange, onValueChange, onEntryRemove, disabled, availableKeys, availableValues} = props; diff --git a/src/components/atoms/KeyValueInput/KeyValueInput.tsx b/src/components/atoms/KeyValueInput/KeyValueInput.tsx index 75f7d6ef70..d94e7e5343 100644 --- a/src/components/atoms/KeyValueInput/KeyValueInput.tsx +++ b/src/components/atoms/KeyValueInput/KeyValueInput.tsx @@ -52,11 +52,13 @@ function KeyValueInput(props: KeyValueInputProps) { return; } + const availableValues: string[] | undefined = data[key]; + if (value === null) { newEntries.push({ id: uuidv4(), key, - value: ANY_VALUE, + value: availableValues?.length ? ANY_VALUE : undefined, }); } else { newEntries.push({ @@ -93,10 +95,13 @@ function KeyValueInput(props: KeyValueInputProps) { const updateEntryKey = (entryId: string, key: string) => { const newEntries = Array.from(entries); const entryIndex = newEntries.findIndex(e => e.id === entryId); + + const availableValues: string[] | undefined = data[key]; + newEntries[entryIndex] = { id: entryId, key, - value: ANY_VALUE, + value: availableValues?.length ? ANY_VALUE : undefined, }; setEntries(newEntries); updateKeyValue(newEntries); diff --git a/src/components/atoms/KeyValueInput/ValueInput.tsx b/src/components/atoms/KeyValueInput/ValueInput.tsx new file mode 100644 index 0000000000..62982b84f7 --- /dev/null +++ b/src/components/atoms/KeyValueInput/ValueInput.tsx @@ -0,0 +1,39 @@ +import {Input, Select} from 'antd'; + +import {ANY_VALUE} from './constants'; + +type ValueInputProps = { + value?: string; + valueType: string; + availableValues?: string[]; + onChange: (newValue: string) => void; + disabled?: boolean; +}; + +const ValueInput: React.FC = props => { + const {value, valueType, availableValues, disabled, onChange} = props; + + // TODO: decide if we need a custom input for the stringArray value type + if (valueType === 'string' || valueType === 'stringArray') { + if (availableValues?.length) { + return ( + + ); + } + return onChange(e.target.value)} disabled={disabled} />; + } + + // TODO: decide if we want to implement more value types + return null; +}; + +export default ValueInput; From fe427a91b74c7435bb6cddf3ff2a4e64f4f1fea5 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Fri, 18 Feb 2022 13:25:10 +0200 Subject: [PATCH 08/12] feat: add KeyValueInput for preview conf opts --- .../PreviewConfigurationEditor.tsx | 34 +++++++++++++++++-- src/constants/helmOptions.ts | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx index 82ccdd23ff..5a5451b657 100644 --- a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx +++ b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx @@ -1,8 +1,25 @@ -import {Input} from 'antd'; +import {useState} from 'react'; + +import {Input, Select} from 'antd'; + +import {helmInstallOptions, helmTemplateOptions} from '@constants/helmOptions'; + +import {useAppSelector} from '@redux/hooks'; + +import {KeyValueInput} from '@components/atoms'; import * as S from './styled'; const PreviewConfigurationEditor = () => { + const helmPreviewMode = useAppSelector( + state => state.config.projectConfig?.settings?.helmPreviewMode || state.config.settings.helmPreviewMode + ); + + const [helmOptions, setHelmOptions] = useState({}); + const [helmCommand, setHelmCommand] = useState<'template' | 'install'>(helmPreviewMode || 'template'); + + const keyValueInputSchema = helmCommand === 'template' ? helmTemplateOptions : helmInstallOptions; + return (
@@ -14,7 +31,20 @@ const PreviewConfigurationEditor = () => { Drag and drop to specify order - Specify options: + Select which helm command to use for this Preview: + + + +
); diff --git a/src/constants/helmOptions.ts b/src/constants/helmOptions.ts index 9b7e88ddf9..cd8698d875 100644 --- a/src/constants/helmOptions.ts +++ b/src/constants/helmOptions.ts @@ -26,7 +26,7 @@ const helmCommonOptions = { '--skip-crds': 'boolean', '--timeout': 'duration', '--username': 'string', - '--values': 'strings', + '--values': 'string', '--verify': 'boolean', '--version': 'string', '--wait': 'boolean', From 4958395edf9d217981cfd089fba278aa33c05e38 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:43:28 +0200 Subject: [PATCH 09/12] feat: OrderedList atom component --- src/App.tsx | 10 +-- .../atoms/OrderedList/OrderedList.tsx | 66 +++++++++++++++++++ src/components/atoms/OrderedList/index.ts | 2 + src/components/atoms/OrderedList/styled.tsx | 12 ++++ src/components/atoms/index.ts | 1 + .../PreviewConfigurationEditor.tsx | 28 +++++++- src/models/appstate.ts | 4 ++ src/models/ui.ts | 1 - .../PreviewConfigurationQuickAction.tsx | 4 +- src/redux/initialState.ts | 4 +- src/redux/reducers/main.ts | 15 +++++ src/redux/reducers/ui.ts | 8 --- src/utils/array.ts | 23 +++++++ 13 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 src/components/atoms/OrderedList/OrderedList.tsx create mode 100644 src/components/atoms/OrderedList/index.ts create mode 100644 src/components/atoms/OrderedList/styled.tsx create mode 100644 src/utils/array.ts diff --git a/src/App.tsx b/src/App.tsx index 40cf71f7b7..f21e79c3ab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,12 +20,8 @@ import {useAppSelector} from '@redux/hooks'; import {setAlert} from '@redux/reducers/alert'; import {setCreateProject, setLoadingProject, setOpenProject} from '@redux/reducers/appConfig'; import {closePluginsDrawer} from '@redux/reducers/extension'; -import { - closeFolderExplorer, - closePreviewConfigurationEditor, - toggleNotifications, - toggleSettings, -} from '@redux/reducers/ui'; +import {closePreviewConfigurationEditor} from '@redux/reducers/main'; +import {closeFolderExplorer, toggleNotifications, toggleSettings} from '@redux/reducers/ui'; import {isInClusterModeSelector, kubeConfigContextSelector, kubeConfigPathSelector} from '@redux/selectors'; import {loadContexts} from '@redux/thunks/loadKubeConfig'; @@ -78,7 +74,7 @@ const App = () => { const dispatch = useDispatch(); const isChangeFiltersConfirmModalVisible = useAppSelector(state => state.main.filtersToBeChanged); const isClusterDiffModalVisible = useAppSelector(state => state.ui.isClusterDiffVisible); - const isPreviewConfigurationEditorOpen = useAppSelector(state => state.ui.isPreviewConfigurationEditorOpen); + const isPreviewConfigurationEditorOpen = useAppSelector(state => state.main.prevConfEditor.isOpen); const isClusterSelectorVisible = useAppSelector(state => state.config.isClusterSelectorVisible); const isCreateFolderModalVisible = useAppSelector(state => state.ui.createFolderModal.isOpen); const isCreateProjectModalVisible = useAppSelector(state => state.ui.createProjectModal.isOpen); diff --git a/src/components/atoms/OrderedList/OrderedList.tsx b/src/components/atoms/OrderedList/OrderedList.tsx new file mode 100644 index 0000000000..db06271ac8 --- /dev/null +++ b/src/components/atoms/OrderedList/OrderedList.tsx @@ -0,0 +1,66 @@ +import {useCallback} from 'react'; + +import {Checkbox} from 'antd'; + +import {ArrowDownOutlined, ArrowUpOutlined} from '@ant-design/icons'; + +import {arrayMove} from '@utils/array'; + +import * as S from './styled'; + +export type OrderedListItem = { + id: string; + text: string; + isChecked: boolean; +}; + +type OrderedListProps = { + items: OrderedListItem[]; + onChange: (items: OrderedListItem[]) => void; +}; + +const OrderedList: React.FC = props => { + const {items, onChange} = props; + + const checkItem = useCallback( + (itemId: string) => { + onChange(items.slice().map(item => (item.id === itemId ? {...item, isChecked: !item.isChecked} : item))); + }, + [items, onChange] + ); + + const moveItem = useCallback( + (itemId: string, direction: 'up' | 'down') => { + const coefficient = direction === 'up' ? -1 : 1; + const itemIndex = items.findIndex(i => i.id === itemId); + if (!itemIndex) { + return; + } + onChange(arrayMove(items, itemIndex, itemIndex + coefficient)); + }, + [items, onChange] + ); + + return ( + + {items.map((item, index) => ( + // eslint-disable-next-line react/no-array-index-key + + + {index + 1}. + checkItem(item.id)}> + + {item.text} + + + + moveItem(item.id, 'up')} /> + moveItem(item.id, 'down')} /> + + + ))} + + ); +}; + +export default OrderedList; diff --git a/src/components/atoms/OrderedList/index.ts b/src/components/atoms/OrderedList/index.ts new file mode 100644 index 0000000000..79b097ae5c --- /dev/null +++ b/src/components/atoms/OrderedList/index.ts @@ -0,0 +1,2 @@ +export {default} from './OrderedList'; +export type {OrderedListItem} from './OrderedList'; diff --git a/src/components/atoms/OrderedList/styled.tsx b/src/components/atoms/OrderedList/styled.tsx new file mode 100644 index 0000000000..0c3c0dae0c --- /dev/null +++ b/src/components/atoms/OrderedList/styled.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +export const List = styled.ol` + padding: 0; + margin-top: 8px; +`; + +export const ListItem = styled.li` + display: flex; + width: 100%; + justify-content: space-between; +`; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index a342a545d7..2eff6925a9 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -15,3 +15,4 @@ export {default as Dots} from './Dots'; export {default as Spinner} from './Spinner'; export {default as Icon} from './Icon'; export {default as TabHeader} from './TabHeader'; +export {default as OrderedList} from './OrderedList'; diff --git a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx index 5a5451b657..7e7725373f 100644 --- a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx +++ b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx @@ -4,9 +4,12 @@ import {Input, Select} from 'antd'; import {helmInstallOptions, helmTemplateOptions} from '@constants/helmOptions'; +import {HelmValuesFile} from '@models/helm'; + import {useAppSelector} from '@redux/hooks'; -import {KeyValueInput} from '@components/atoms'; +import {KeyValueInput, OrderedList} from '@components/atoms'; +import {OrderedListItem} from '@components/atoms/OrderedList'; import * as S from './styled'; @@ -15,11 +18,33 @@ const PreviewConfigurationEditor = () => { state => state.config.projectConfig?.settings?.helmPreviewMode || state.config.settings.helmPreviewMode ); + const helmChart = useAppSelector(state => { + const helmChartId = state.main.prevConfEditor.helmChartId; + if (!helmChartId) { + return undefined; + } + return state.main.helmChartMap[helmChartId]; + }); + + const valuesFiles = useAppSelector( + state => + helmChart?.valueFileIds + .map(id => state.main.helmValuesMap[id]) + .filter((v): v is HelmValuesFile => v !== undefined) || [] + ); + + const [valuesFileItems, setValuesFileItems] = useState( + valuesFiles.map(vf => ({id: vf.id, text: vf.name, isChecked: false})) + ); const [helmOptions, setHelmOptions] = useState({}); const [helmCommand, setHelmCommand] = useState<'template' | 'install'>(helmPreviewMode || 'template'); const keyValueInputSchema = helmCommand === 'template' ? helmTemplateOptions : helmInstallOptions; + if (!helmChart) { + return

Something went wrong, could not find the helm chart.

; + } + return (
@@ -29,6 +54,7 @@ const PreviewConfigurationEditor = () => { Select which values files to use: Drag and drop to specify order + Select which helm command to use for this Preview: diff --git a/src/models/appstate.ts b/src/models/appstate.ts index 7fc4a4164c..a2f55b9de0 100644 --- a/src/models/appstate.ts +++ b/src/models/appstate.ts @@ -146,6 +146,10 @@ interface AppState { /** type/value of filters that will be changed */ filtersToBeChanged?: ResourceFilterType; registeredKindHandlers: string[]; + prevConfEditor: { + isOpen: boolean; + helmChartId?: string; + }; } export type { diff --git a/src/models/ui.ts b/src/models/ui.ts index c7ff8104e0..10a77df9de 100644 --- a/src/models/ui.ts +++ b/src/models/ui.ts @@ -60,7 +60,6 @@ export type UiState = { isSettingsOpen: boolean; isClusterDiffVisible: boolean; isNotificationsOpen: boolean; - isPreviewConfigurationEditorOpen: boolean; newResourceWizard: { isOpen: boolean; defaultInput?: NewResourceWizardInput; diff --git a/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx index 5d9e8459f4..0b80169927 100644 --- a/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx +++ b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx @@ -9,7 +9,7 @@ import styled from 'styled-components'; import {SectionCustomComponentProps} from '@models/navigator'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; -import {openPreviewConfigurationEditor} from '@redux/reducers/ui'; +import {openPreviewConfigurationEditor} from '@redux/reducers/main'; import Colors from '@styles/Colors'; @@ -35,7 +35,7 @@ const PreviewConfigurationNameSuffix: React.FC = pr const dispatch = useAppDispatch(); const onClick = () => { - dispatch(openPreviewConfigurationEditor()); + dispatch(openPreviewConfigurationEditor(sectionInstance.id.replace('-configurations', ''))); }; return ( diff --git a/src/redux/initialState.ts b/src/redux/initialState.ts index d5d92731cf..8e8c1870f6 100644 --- a/src/redux/initialState.ts +++ b/src/redux/initialState.ts @@ -46,6 +46,9 @@ const initialAppState: AppState = { shouldEditorReloadSelectedPath: false, checkedResourceIds: [], registeredKindHandlers: [], + prevConfEditor: { + isOpen: false, + }, }; const initialAppConfigState: AppConfig = { @@ -101,7 +104,6 @@ const initialUiState: UiState = { isClusterDiffVisible: false, isNotificationsOpen: false, isFolderLoading: false, - isPreviewConfigurationEditorOpen: false, quickSearchActionsPopup: { isOpen: false, }, diff --git a/src/redux/reducers/main.ts b/src/redux/reducers/main.ts index fa7fc370e9..f97ea37718 100644 --- a/src/redux/reducers/main.ts +++ b/src/redux/reducers/main.ts @@ -696,6 +696,19 @@ export const mainSlice = createSlice({ notification.hasSeen = true; }); }, + openPreviewConfigurationEditor: (state: Draft, action: PayloadAction) => { + const helmChartId = action.payload; + state.prevConfEditor = { + helmChartId, + isOpen: true, + }; + }, + closePreviewConfigurationEditor: (state: Draft) => { + state.prevConfEditor = { + isOpen: false, + helmChartId: undefined, + }; + }, }, extraReducers: builder => { builder.addCase(setAlert, (state, action) => { @@ -1141,5 +1154,7 @@ export const { addMultipleKindHandlers, addKindHandler, seenNotifications, + openPreviewConfigurationEditor, + closePreviewConfigurationEditor, } = mainSlice.actions; export default mainSlice.reducer; diff --git a/src/redux/reducers/ui.ts b/src/redux/reducers/ui.ts index 9285a4dbc7..c43d94bbb6 100644 --- a/src/redux/reducers/ui.ts +++ b/src/redux/reducers/ui.ts @@ -223,12 +223,6 @@ export const uiSlice = createSlice({ state.highlightedItems.browseTemplates = action.payload === HighlightItems.BROWSE_TEMPLATES; state.highlightedItems.connectToCluster = action.payload === HighlightItems.CONNECT_TO_CLUSTER; }, - openPreviewConfigurationEditor: (state: Draft) => { - state.isPreviewConfigurationEditorOpen = true; - }, - closePreviewConfigurationEditor: (state: Draft) => { - state.isPreviewConfigurationEditorOpen = false; - }, }, extraReducers: builder => { builder @@ -294,7 +288,5 @@ export const { closeSaveResourcesToFileFolderModal, zoomIn, zoomOut, - openPreviewConfigurationEditor, - closePreviewConfigurationEditor, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 0000000000..db6bb40273 --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,23 @@ +/** + * Mutates an array by moving an element from an index to another + * @param array + * @param fromIndex + * @param toIndex + */ +export function arrayMoveMutate(array: T[], fromIndex: number, toIndex: number) { + const startIndex = fromIndex < 0 ? array.length + fromIndex : fromIndex; + if (startIndex >= 0 && startIndex < array.length) { + const endIndex = toIndex < 0 ? array.length + toIndex : toIndex; + const [element] = array.splice(fromIndex, 1); + array.splice(endIndex, 0, element); + } +} + +/** + * Returns a copy of the array with an element moved from an index to another + */ +export function arrayMove(array: T[], fromIndex: number, toIndex: number) { + const arrayCopy = array.slice(); + arrayMoveMutate(arrayCopy, fromIndex, toIndex); + return arrayCopy; +} From c814d1cfb49d97b807b83f113deaed28545e1155 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:47:23 +0200 Subject: [PATCH 10/12] feat: add prev conf action buttons --- .../PreviewConfigurationEditor.tsx | 11 ++++++++++- .../organisms/PreviewConfigurationEditor/styled.tsx | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx index 7e7725373f..3fd0d3262d 100644 --- a/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx +++ b/src/components/organisms/PreviewConfigurationEditor/PreviewConfigurationEditor.tsx @@ -1,6 +1,6 @@ import {useState} from 'react'; -import {Input, Select} from 'antd'; +import {Button, Input, Select} from 'antd'; import {helmInstallOptions, helmTemplateOptions} from '@constants/helmOptions'; @@ -72,6 +72,15 @@ const PreviewConfigurationEditor = () => { onChange={setHelmOptions} /> + + + + +
); }; diff --git a/src/components/organisms/PreviewConfigurationEditor/styled.tsx b/src/components/organisms/PreviewConfigurationEditor/styled.tsx index 68a7db3edb..6b30367f77 100644 --- a/src/components/organisms/PreviewConfigurationEditor/styled.tsx +++ b/src/components/organisms/PreviewConfigurationEditor/styled.tsx @@ -20,3 +20,10 @@ export const Description = styled.p` font-size: 14px; color: ${Colors.grey7}; `; + +export const ActionsContainer = styled.div` + margin-top: 12px; + display: flex; + justify-content: flex-end; + gap: 8px; +`; From 7fbfcce52ba70a8d24eced785cdfe08b4f133f35 Mon Sep 17 00:00:00 2001 From: Catalin <20538711+devcatalin@users.noreply.github.com> Date: Sat, 19 Feb 2022 20:19:58 +0200 Subject: [PATCH 11/12] chore: add new prev conf tooltip constant --- src/constants/tooltips.ts | 1 + .../PreviewConfigurationQuickAction.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/constants/tooltips.ts b/src/constants/tooltips.ts index 8b772b4635..f4ee38df3c 100644 --- a/src/constants/tooltips.ts +++ b/src/constants/tooltips.ts @@ -56,3 +56,4 @@ export const SearchProjectTooltip = 'Search for project by name or path'; export const PluginDrawerTooltip = 'Open Plugins Manager'; export const QuickFilterTooltip = `Filter results – Hint: quick-filter using ${KEY_CTRL_CMD} + P`; export const NewResourceTooltip = `Create new resource (${KEY_CTRL_CMD} + N)`; +export const NewPreviewConfigurationTooltip = 'Create a new Preview Configuration'; diff --git a/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx index 0b80169927..3e521b98e0 100644 --- a/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx +++ b/src/navsections/HelmChartSectionBlueprint/PreviewConfigurationQuickAction.tsx @@ -6,6 +6,8 @@ import {PlusOutlined} from '@ant-design/icons'; import styled from 'styled-components'; +import {NewPreviewConfigurationTooltip} from '@constants/tooltips'; + import {SectionCustomComponentProps} from '@models/navigator'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; @@ -41,7 +43,7 @@ const PreviewConfigurationNameSuffix: React.FC = pr return ( - + + {docsUrl && ( + + )} {entries.map(entry => ( { const [helmOptions, setHelmOptions] = useState({}); const [helmCommand, setHelmCommand] = useState<'template' | 'install'>(helmPreviewMode || 'template'); - const keyValueInputSchema = helmCommand === 'template' ? helmTemplateOptions : helmInstallOptions; + const keyValueInputSchema = useMemo( + () => (helmCommand === 'template' ? helmTemplateOptions : helmInstallOptions), + [helmCommand] + ); + + const helmOptionsDocsUrl = useMemo( + () => (helmCommand === 'template' ? HELM_TEMPLATE_OPTIONS_DOCS_URL : HELM_INSTALL_OPTIONS_DOCS_URL), + [helmCommand] + ); if (!helmChart) { return

Something went wrong, could not find the helm chart.

; @@ -69,6 +78,7 @@ const PreviewConfigurationEditor = () => { value={helmOptions} schema={keyValueInputSchema} data={{}} + docsUrl={helmOptionsDocsUrl} onChange={setHelmOptions} /> diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 48b78b77e0..b7295ffd84 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -30,3 +30,5 @@ export const DEFAULT_PLUGINS = [ export const PLUGIN_DOCS_URL = 'https://kubeshop.github.io/monokle/plugins/'; export const HELM_CHART_ENTRY_FILE = 'Chart.yaml'; export const HELM_CHART_SECTION_NAME = 'Helm Charts'; +export const HELM_TEMPLATE_OPTIONS_DOCS_URL = 'https://helm.sh/docs/helm/helm_template/#options'; +export const HELM_INSTALL_OPTIONS_DOCS_URL = 'https://helm.sh/docs/helm/helm_install/#options';