From e22c7bd8b06fa715ed7a0ce8d5e61aade0ccd2cb Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Fri, 15 Nov 2024 12:13:25 +0530 Subject: [PATCH 01/23] test(ui): added unit test case for enable count --- .../components/table/stories/rowDataMockup.ts | 100 ++++++++++++++++++ .../table/tests/TableWrapper.test.tsx | 35 +++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/ui/src/components/table/stories/rowDataMockup.ts b/ui/src/components/table/stories/rowDataMockup.ts index f36b58b08..fa52b2a25 100644 --- a/ui/src/components/table/stories/rowDataMockup.ts +++ b/ui/src/components/table/stories/rowDataMockup.ts @@ -394,6 +394,98 @@ export const ROW_DATA = [ }, ]; +export const ROW_DATA_FOR_COUNT = [ + ...ROW_DATA, + { + name: 'testsomethingelse1', + id: 'https://localhost:8000/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/testsomethingelse', + updated: '1970-01-01T00:00:00+00:00', + links: { + alternate: + '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/testsomethingelse', + list: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/testsomethingelse', + edit: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/testsomethingelse', + remove: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/testsomethingelse', + }, + author: 'admin', + acl: { + app: 'Splunk_TA_UCCExample', + can_change_perms: true, + can_list: true, + can_share_app: true, + can_share_global: true, + can_share_user: true, + can_write: true, + modifiable: true, + owner: 'admin', + perms: { + read: ['*'], + write: ['admin', 'sc_admin'], + }, + removable: true, + sharing: 'global', + }, + content: { + account_multiple_select: 'two', + account_radio: '1', + auth_type: 'basic', + custom_endpoint: 'login.example.com', + disabled: true, + 'eai:acl': null, + 'eai:appName': 'Splunk_TA_UCCExample', + 'eai:userName': 'nobody', + password: '******', + custom_text: 'testsomethingelse', + token: '******', + username: 'test1', + }, + }, + { + name: 'zzzzzzzz', + id: 'https://localhost:8000/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/zzzzzzz', + updated: '1970-01-01T00:00:00+00:00', + links: { + alternate: + '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/zzzzzzz', + list: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/zzzzzzz', + edit: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/zzzzzzz', + remove: '/servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account/zzzzzzz', + }, + author: 'admin', + acl: { + app: 'Splunk_TA_UCCExample', + can_change_perms: true, + can_list: true, + can_share_app: true, + can_share_global: true, + can_share_user: true, + can_write: true, + modifiable: true, + owner: 'admin', + perms: { + read: ['*'], + write: ['admin', 'sc_admin'], + }, + removable: true, + sharing: 'global', + }, + content: { + account_multiple_select: 'one', + account_radio: '1', + auth_type: 'basic', + custom_endpoint: 'login.example.com', + disabled: true, + 'eai:acl': null, + 'eai:appName': 'Splunk_TA_UCCExample', + 'eai:userName': 'nobody', + password: '******', + custom_text: '222222', + token: '******', + username: 'zzzzz', + }, + }, +]; + export const MockRowData = { links: { create: `/servicesNS/nobody/-/splunk_ta_uccexample_account/_new`, @@ -402,6 +494,14 @@ export const MockRowData = { entry: ROW_DATA, messages: [], }; +export const MockRowDataForStatusCount = { + links: { + create: `/servicesNS/nobody/-/splunk_ta_uccexample_account/_new`, + }, + updated: '2023-08-21T11:54:12+00:00', + entry: ROW_DATA_FOR_COUNT, + messages: [], +}; export const ServerHandlers = [ http.get(`/servicesNS/nobody/-/splunk_ta_uccexample_account`, () => diff --git a/ui/src/components/table/tests/TableWrapper.test.tsx b/ui/src/components/table/tests/TableWrapper.test.tsx index df1d27445..918c31ca9 100644 --- a/ui/src/components/table/tests/TableWrapper.test.tsx +++ b/ui/src/components/table/tests/TableWrapper.test.tsx @@ -3,12 +3,16 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { http, HttpResponse } from 'msw'; import { BrowserRouter } from 'react-router-dom'; -import { MockRowData } from '../stories/rowDataMockup'; +import { MockRowData, MockRowDataForStatusCount } from '../stories/rowDataMockup'; import TableWrapper, { ITableWrapperProps } from '../TableWrapper'; import { server } from '../../../mocks/server'; import { TableContextProvider } from '../../../context/TableContext'; import { setUnifiedConfig } from '../../../util/util'; -import { getSimpleConfigWithMapping, SIMPLE_NAME_TABLE_MOCK_DATA } from '../stories/configMockups'; +import { + getSimpleConfigStylePage, + getSimpleConfigWithMapping, + SIMPLE_NAME_TABLE_MOCK_DATA, +} from '../stories/configMockups'; jest.mock('immutability-helper'); @@ -183,3 +187,30 @@ it('Correctly render status labels with mapped values', async () => { const inActiveStatusCell = within(inActiveRow).getByTestId('status'); expect(inActiveStatusCell).toHaveTextContent('Disabled Field'); }); + +it('Check inputs count is visible', async () => { + const props = { + page: 'inputs', + serviceName: 'example_input_one', + handleRequestModalOpen, + handleOpenPageStyleDialog, + displayActionBtnAllRows: false, + } satisfies ITableWrapperProps; + + server.use( + http.get('/servicesNS/nobody/-/splunk_ta_uccexample_example_input_one', () => + HttpResponse.json(MockRowDataForStatusCount) + ) + ); + + setUnifiedConfig(getSimpleConfigStylePage()); + + render( + + + , + { wrapper: BrowserRouter } + ); + const statusCount = await screen.findByText('11 Inputs (7 of 11 enabled)'); + expect(statusCount).toBeInTheDocument(); +}); From 9e22d4f2ec58200e0ec51f1095b7e0ee35ad9990 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Fri, 29 Nov 2024 14:34:08 +0530 Subject: [PATCH 02/23] feat(checkboxTree): added the layout for new component for checkbox tree --- .../CheckboxTree/CheckboxSubTree.tsx | 109 +++++++++++ .../components/CheckboxTree/CheckboxTree.tsx | 173 ++++++++++++++++++ .../CheckboxTree/CheckboxTree.utils.ts | 73 ++++++++ .../CheckboxTree/CheckboxTreeRow.tsx | 37 ++++ .../CheckboxTree/CheckboxTreeRowWrapper.tsx | 27 +++ .../CheckboxTree/StyledComponent.tsx | 72 ++++++++ ui/src/components/CheckboxTree/types.ts | 45 +++++ ui/src/components/CheckboxTree/utils.ts | 32 ++++ 8 files changed, 568 insertions(+) create mode 100644 ui/src/components/CheckboxTree/CheckboxSubTree.tsx create mode 100644 ui/src/components/CheckboxTree/CheckboxTree.tsx create mode 100644 ui/src/components/CheckboxTree/CheckboxTree.utils.ts create mode 100644 ui/src/components/CheckboxTree/CheckboxTreeRow.tsx create mode 100644 ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx create mode 100644 ui/src/components/CheckboxTree/StyledComponent.tsx create mode 100644 ui/src/components/CheckboxTree/types.ts create mode 100644 ui/src/components/CheckboxTree/utils.ts diff --git a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx new file mode 100644 index 000000000..5cb8db0e2 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx @@ -0,0 +1,109 @@ +import React, { useMemo, useState } from 'react'; +import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; +import { getCheckedCheckboxesCount, GroupWithRows, ValueByField } from './CheckboxTree.utils'; +import { + CheckboxContainer, + CheckboxWrapper, + Description, + GroupLabel, + RowContainer, + StyledCollapsiblePanel, +} from './StyledComponent'; + +interface CheckboxSubTreeProps { + group: GroupWithRows; + values: ValueByField; + handleRowChange: (newValue: { field: string; checkbox: boolean; text?: string }) => void; + disabled?: boolean; + handleParentCheckboxTree: (groupLabel: string, newCheckboxValue: boolean) => void; +} + +const CheckboxSubTree: React.FC = ({ + group, + values, + handleRowChange, + disabled, + handleParentCheckboxTree, +}) => { + const [isExpanded, setIsExpanded] = useState(true); + + const isParentChecked = useMemo( + () => group.rows.every((row) => values.get(row.field)?.checkbox), + [group.rows, values] + ); + + const isIndeterminate = useMemo( + () => group.rows.some((row) => values.get(row.field)?.checkbox) && !isParentChecked, + [group.rows, values, isParentChecked] + ); + + const checkedCheckboxesCount = useMemo( + () => getCheckedCheckboxesCount(group, values), + [group, values] + ); + + const toggleCollapse = () => setIsExpanded((prev) => !prev); + + const ParentCheckbox = ( + + { + const inputElement = el as HTMLInputElement | null; + if (inputElement) { + inputElement.indeterminate = isIndeterminate; + } + }} + onChange={() => handleParentCheckboxTree(group.label, !isParentChecked)} + disabled={disabled} + /> + {group.label} + + ); + + const childRows = ( + + {group.rows.map((row) => ( + + ))} + + ); + + const description = ( + + {checkedCheckboxesCount} of {group.fields.length} + + ); + + return ( + + {group.options?.isExpandable ? ( + + {childRows} + + ) : ( + <> + + {ParentCheckbox} + {description} + + {childRows} + + )} + + ); +}; + +export default CheckboxSubTree; diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx new file mode 100644 index 000000000..9c4cfc9f1 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -0,0 +1,173 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import ColumnLayout from '@splunk/react-ui/ColumnLayout'; +import Button from '@splunk/react-ui/Button'; +import Search from '@splunk/react-ui/Search'; +import { StyledColumnLayout } from './StyledComponent'; +import { + getDefaultValues, + getFlattenRowsWithGroups, + getNewCheckboxValues, + isGroupWithRows, +} from './CheckboxTree.utils'; +import CheckboxSubTree from './CheckboxSubTree'; +import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; +import { MODE_CREATE } from '../../constants/modes'; +import { CheckboxTreeProps, ValueByField } from './types'; +import { packValue, parseValue } from './utils'; + +type SearchChangeData = { + value: string; +}; + +function CheckboxTree(props: CheckboxTreeProps) { + const { field, handleChange, controlOptions, disabled } = props; + const flattenedRowsWithGroups = getFlattenRowsWithGroups(controlOptions); + const shouldUseDefaultValue = + props.mode === MODE_CREATE && (props.value === null || props.value === undefined); + const initialValues = shouldUseDefaultValue + ? getDefaultValues(controlOptions.rows) + : parseValue(props.value); + + const [values, setValues] = useState(initialValues); + const [searchForCheckBoxValue, setSearchForCheckBoxValue] = useState(''); + + // Propagate default values on mount if applicable + useEffect(() => { + if (shouldUseDefaultValue) { + handleChange(field, packValue(initialValues), 'CheckboxTree'); + } + }, [field, handleChange, shouldUseDefaultValue, initialValues]); + + const handleRowChange = useCallback( + (newValue: { field: string; checkbox: boolean; text?: string }) => { + setValues((prevValues: ValueByField) => { + const updatedValues = getNewCheckboxValues(prevValues, newValue); + handleChange(field, packValue(updatedValues), 'CheckboxTree'); + return updatedValues; + }); + }, + [field, handleChange] + ); + + const handleParentCheckboxTree = useCallback( + (groupLabel: string, newCheckboxValue: boolean) => { + if (!controlOptions?.groups) { + return; + } + + const group = controlOptions.groups.find((g) => g.label === groupLabel); + if (!group) { + return; + } + + setValues((prevValues) => { + const updatedValues = new Map(prevValues); + group.fields.forEach((item) => { + updatedValues.set(item, { checkbox: newCheckboxValue }); + }); + handleChange(field, packValue(updatedValues), 'CheckboxTree'); + return updatedValues; + }); + }, + [controlOptions, field, handleChange] + ); + + const handleCheckboxToggleAll = useCallback( + (newCheckboxValue: boolean) => { + setValues((prevValues) => { + const updatedValues = new Map(prevValues); + controlOptions.rows.forEach((row) => { + updatedValues.set(row.field, { checkbox: newCheckboxValue }); + }); + handleChange(field, packValue(updatedValues), 'CheckboxTree'); + return updatedValues; + }); + }, + [controlOptions.rows, field, handleChange] + ); + + const handleSearchChange = useCallback( + (e: React.SyntheticEvent, { value: searchValue }: SearchChangeData) => { + setSearchForCheckBoxValue(searchValue); + }, + [] + ); + + const filterRows = useCallback(() => { + const searchValueLower = searchForCheckBoxValue.toLowerCase(); + + return flattenedRowsWithGroups + .flatMap((row) => { + if (isGroupWithRows(row)) { + const groupMatches = row.label.toLowerCase().includes(searchValueLower); + const filteredRows = groupMatches + ? row.rows + : row.rows.filter((childRow) => + childRow.checkbox?.label?.toLowerCase().includes(searchValueLower) + ); + + return groupMatches || filteredRows.length > 0 + ? { ...row, rows: filteredRows } + : []; + } + + const rowMatches = row.checkbox?.label?.toLowerCase().includes(searchValueLower); + return rowMatches ? row : null; + }) + .filter(Boolean); + }, [flattenedRowsWithGroups, searchForCheckBoxValue]); + + const filteredRows = filterRows(); + + return ( + <> + + + {filteredRows.map((row) => + row && isGroupWithRows(row) ? ( + + + + ) : ( + row && ( + + + + ) + ) + )} + + +
+
+ + ); +} + +export default CheckboxTree; diff --git a/ui/src/components/CheckboxTree/CheckboxTree.utils.ts b/ui/src/components/CheckboxTree/CheckboxTree.utils.ts new file mode 100644 index 000000000..f9d066266 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTree.utils.ts @@ -0,0 +1,73 @@ +import { CheckboxTreeProps, Field, GroupWithRows, Row, Value, ValueByField } from './types'; + +export function isGroupWithRows(item: GroupWithRows | Row): item is GroupWithRows { + return 'label' in item; +} + +export function getFlattenRowsWithGroups({ groups, rows }: CheckboxTreeProps['controlOptions']) { + const flattenRowsMixedWithGroups: Array = []; + + rows.forEach((row) => { + const groupForThisRow = groups?.find((group) => group.fields.includes(row.field)); + if (groupForThisRow) { + const addedGroup = flattenRowsMixedWithGroups.find( + (item): item is GroupWithRows => + isGroupWithRows(item) && item.label === groupForThisRow.label + ); + const groupToAdd = addedGroup || { + ...groupForThisRow, + rows: [], + }; + groupToAdd.rows.push(row); + if (!addedGroup) { + flattenRowsMixedWithGroups.push(groupToAdd); + } + return; + } + flattenRowsMixedWithGroups.push(row); + }); + + return flattenRowsMixedWithGroups; +} + +export function getNewCheckboxValues( + values: ValueByField, + newValue: { + field: string; + checkbox: boolean; + } +) { + const newValues = new Map(values); + newValues.set(newValue.field, { + checkbox: newValue.checkbox, + }); + + return newValues; +} + +export function getCheckedCheckboxesCount(group: GroupWithRows, values: ValueByField) { + let checkedCheckboxesCount = 0; + group.rows.forEach((row) => { + if (values.get(row.field)?.checkbox) { + checkedCheckboxesCount += 1; + } + }); + return checkedCheckboxesCount; +} + +export function getDefaultValues(rows: Row[]): ValueByField { + const resultMap = new Map(); + + rows.forEach((row) => { + if (!isGroupWithRows(row)) { + const checkboxDefaultValue = row.checkbox?.defaultValue; + if (typeof checkboxDefaultValue === 'boolean') { + resultMap.set(row.field, { + checkbox: checkboxDefaultValue, + }); + } + } + }); + + return resultMap; +} diff --git a/ui/src/components/CheckboxTree/CheckboxTreeRow.tsx b/ui/src/components/CheckboxTree/CheckboxTreeRow.tsx new file mode 100644 index 000000000..28ad92628 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTreeRow.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { StyledRow, StyledSwitch } from './StyledComponent'; + +interface CheckboxRowProps { + field: string; + label: string; + checkbox: boolean; + disabled?: boolean; + handleChange: (value: { field: string; checkbox: boolean }) => void; +} + +function CheckboxRow(props: CheckboxRowProps) { + const { field, label, checkbox, disabled, handleChange } = props; + const handleChangeCheckbox = ( + event: React.MouseEvent, + data: { selected: boolean } + ) => { + handleChange({ field, checkbox: !data.selected }); + }; + + return ( + + + {label} + + + ); +} + +export default CheckboxRow; diff --git a/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx new file mode 100644 index 000000000..7e604a2fb --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import CheckboxRow from './CheckboxTreeRow'; +import { Row, ValueByField } from './types'; + +function CheckboxRowWrapper({ + row, + values, + handleRowChange, + disabled, +}: { + row: Row; + values: ValueByField; + handleRowChange: (newValue: { field: string; checkbox: boolean; text?: string }) => void; + disabled?: boolean; +}) { + const valueForField = values.get(row.field); + return ( + + ); +} +export default CheckboxRowWrapper; diff --git a/ui/src/components/CheckboxTree/StyledComponent.tsx b/ui/src/components/CheckboxTree/StyledComponent.tsx new file mode 100644 index 000000000..546972d83 --- /dev/null +++ b/ui/src/components/CheckboxTree/StyledComponent.tsx @@ -0,0 +1,72 @@ +import styled, { css } from 'styled-components'; +import ColumnLayout from '@splunk/react-ui/ColumnLayout'; +import CollapsiblePanel from '@splunk/react-ui/CollapsiblePanel'; +import { variables } from '@splunk/themes'; +import Switch from '@splunk/react-ui/Switch'; + +export const FixedCheckboxRowWidth = css` + width: 320px; +`; + +export const StyledColumnLayout = styled(ColumnLayout)` + ${FixedCheckboxRowWidth} +`; + +export const CheckboxContainer = styled.div` + display: flex; + flex-direction: column; + flex: 1; +`; + +export const StyledCollapsiblePanel = styled(CollapsiblePanel)` + & > *:not(:last-child) { + background-color: ${variables.neutral300}; + font-size: 14px; + margin-bottom: ${variables.spacingSmall}; + } +`; + +export const RowContainer = styled.div` + & > *:not(:last-child) { + margin-bottom: ${variables.spacingSmall}; + } + margin: 0 0 ${variables.spacingSmall} 28px; +`; + +export const GroupLabel = styled.div` + display: flex; + justify-content: space-between; + padding: 6px ${variables.spacingSmall}; + background-color: ${variables.neutral300}; + font-size: 14px; + margin: ${variables.spacingSmall} 0; +`; + +export const Description = styled.span` + padding-right: ${variables.spacingLarge}; + margin-left: ${variables.spacingSmall}; + font-size: 12px; + display: flex; + justify-content: end; +`; + +export const CheckboxWrapper = styled.div` + display: flex; + align-items: center; + input { + margin-right: ${variables.spacingSmall}; + } +`; + +export const StyledSwitch = styled(Switch)` + padding: 0 3px; + flex: min-content; + align-items: center; +`; + +export const StyledRow = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 2px; +`; diff --git a/ui/src/components/CheckboxTree/types.ts b/ui/src/components/CheckboxTree/types.ts new file mode 100644 index 000000000..bbb6b151d --- /dev/null +++ b/ui/src/components/CheckboxTree/types.ts @@ -0,0 +1,45 @@ +import { Mode } from '../../constants/modes'; + +export type Field = string; +export type Value = { + checkbox: boolean; + inputValue?: number; + error?: string; +}; + +export type ValueByField = Map; + +export interface Group { + label: string; + fields: string[]; + options?: { + isExpandable?: boolean; + expand?: boolean; + }; +} + +export interface Row { + field: string; + checkbox?: { + label?: string; + defaultValue?: boolean; + }; +} + +export type GroupWithRows = Group & { rows: Row[] }; + +export interface CheckboxTreeProps { + field: string; + value?: string; + controlOptions: { + groups?: Group[]; + rows: Row[]; + }; + mode: Mode; + addCustomValidator?: ( + field: string, + validator: (submittedField: string, submittedValue: string) => void + ) => void; + handleChange: (field: string, value: string, componentType?: 'CheckboxTree') => void; + disabled?: boolean; +} diff --git a/ui/src/components/CheckboxTree/utils.ts b/ui/src/components/CheckboxTree/utils.ts new file mode 100644 index 000000000..90d41d279 --- /dev/null +++ b/ui/src/components/CheckboxTree/utils.ts @@ -0,0 +1,32 @@ +import { ValueByField, Field, Value } from './types'; + +export function parseValue(collection?: string): ValueByField { + const resultMap = new Map(); + + if (!collection) { + return resultMap; + } + + const splitValues = collection.split(','); + splitValues.forEach((rawValue) => { + const [field, inputValue] = rawValue.trim().split('/'); + const parsedInputValue = inputValue === '' ? undefined : Number(inputValue); + if (!field || Number.isNaN(parsedInputValue)) { + throw new Error(`Value is not parsable: ${collection}`); + } + + resultMap.set(field, { + checkbox: true, + inputValue: parsedInputValue, + }); + }); + + return resultMap; +} + +export function packValue(map: ValueByField): string { + return Array.from(map.entries()) + .filter(([, value]) => value.checkbox) + .map(([field]) => `${field}`) + .join(','); +} From bd7fd13d07c42bbea8b50117e6922446a0126038 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Wed, 4 Dec 2024 11:36:51 +0530 Subject: [PATCH 03/23] feat(storybook): added storybook and sample in input service --- .../schema/schema.json | 112 +++++++++ .../splunk_ta_uccexample_rh_three_custom.py | 8 + .../globalConfig.json | 76 ++++++- .../splunk_ta_uccexample_rh_three_custom.py | 10 +- .../CheckboxTree/CheckboxSubTree.tsx | 3 +- .../components/CheckboxTree/CheckboxTree.tsx | 12 +- .../CheckboxTree/CheckboxTreeMocks.ts | 26 +++ .../CheckboxTree/StyledComponent.tsx | 7 +- .../stories/CheckboxTree.stories.tsx | 214 ++++++++++++++++++ .../stories/CheckboxTreeInputPage.stories.tsx | 57 +++++ .../stories/CheckboxTreeMocks.json | 101 +++++++++ .../stories/CheckboxTreeRequiredMocks.json | 102 +++++++++ ui/src/components/CheckboxTree/types.ts | 6 + ui/src/components/CheckboxTree/utils.ts | 20 +- ui/src/components/CheckboxTree/validation.ts | 26 +++ ui/src/constants/ControlTypeMap.ts | 2 + ui/src/types/globalConfig/entities.ts | 34 +++ 17 files changed, 787 insertions(+), 29 deletions(-) create mode 100644 ui/src/components/CheckboxTree/CheckboxTreeMocks.ts create mode 100644 ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx create mode 100644 ui/src/components/CheckboxTree/stories/CheckboxTreeInputPage.stories.tsx create mode 100644 ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json create mode 100644 ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json create mode 100644 ui/src/components/CheckboxTree/validation.ts diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index a99243630..8bb77eb7f 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -1449,6 +1449,115 @@ ], "additionalProperties": false }, + "CheckboxTreeEntity": { + "type": "object", + "properties": { + "field": { + "$ref": "#/definitions/Field" + }, + "label": { + "type": "string", + "maxLength": 30, + "description": "Text displayed next to entity field" + }, + "type": { + "const": "CheckboxTree", + "type": "string", + "description": "Exactly: CheckboxTree" + }, + "options": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 30, + "description": "Text displayed next to entity field" + }, + "options": { + "type": "object", + "properties": { + "isExpandable": { + "type": "boolean", + "default": false + }, + "expand": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "required": ["label", "fields"], + "additionalProperties": false + } + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "checkbox": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 30, + "description": "Text displayed next to entity field" + }, + "defaultValue": { + "type": "boolean", + "description": "The initial input value." + } + }, + "additionalProperties": false + } + }, + "required": ["field"], + "additionalProperties": false + }, + "minItems": 1 + }, + "disableonEdit": { + "$ref": "#/definitions/disableonEdit" + } + }, + "required": ["rows"], + "additionalProperties": false + }, + "validators": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/RegexValidator" + } + ] + } + }, + "required": { + "type": "boolean" + } + }, + "required": ["field", "label", "type", "options"], + "additionalProperties": false + }, "RadioEntity": { "type": "object", "properties": { @@ -1864,6 +1973,9 @@ { "$ref": "#/definitions/CheckboxGroupEntity" }, + { + "$ref": "#/definitions/CheckboxTreeEntity" + }, { "$ref": "#/definitions/TextEntity" }, diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py index 8851896c6..3d73f4f2f 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py @@ -30,6 +30,14 @@ validator=None ) + field.RestField( + 'apis', + required=False, + encrypted=False, + default=None, + validator=None + ), + ] model = RestModel(fields, name=None) diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index 877e52159..23510a926 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1571,6 +1571,80 @@ "label": "Interval", "help": "Time interval of the data input, in seconds.", "required": true + }, + { + "type": "CheckboxTree", + "label": "Event Filters", + "field": "apis", + "options": { + "groups": [ + { + "label": "Transactions", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "slow_request", + "transaction_stall" + ] + }, + { + "label": "others", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "custom_events", + "cluster_events", + "network_events" + ] + } + ], + "rows": [ + { + "field": "code_problems", + "checkbox": { + "label": "Code Problems", + "defaultValue": true + } + }, + { + "field": "slow_request", + "checkbox": { + "label": "Slow Request", + "defaultValue": true + } + }, + { + "field": "transaction_stall", + "checkbox": { + "label": "Transactions Stall", + "defaultValue": false + } + }, + { + "field": "custom_events", + "checkbox": { + "label": "Custom Events", + "defaultValue": true + } + }, + { + "field": "cluster_events", + "checkbox": { + "label": "Cluster Events" + } + }, + { + "field": "network_events", + "checkbox": { + "label": "Network Events" + } + } + ] + } } ], "title": "Example Input Three" @@ -1909,7 +1983,7 @@ "version": "5.52.0+70c7e9d6b", "displayName": "Splunk UCC test Add-on", "schemaVersion": "0.0.9", - "_uccVersion": "5.52.0", + "_uccVersion": "5.53.1", "supportedThemes": [ "light", "dark" diff --git a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py index 8851896c6..ea9d65ca5 100644 --- a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py @@ -28,7 +28,15 @@ 'disabled', required=False, validator=None - ) + ), + + field.RestField( + 'apis', + required=False, + encrypted=False, + default=None, + validator=None + ), ] model = RestModel(fields, name=None) diff --git a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx index 5cb8db0e2..de02208f9 100644 --- a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; -import { getCheckedCheckboxesCount, GroupWithRows, ValueByField } from './CheckboxTree.utils'; +import { getCheckedCheckboxesCount } from './CheckboxTree.utils'; import { CheckboxContainer, CheckboxWrapper, @@ -9,6 +9,7 @@ import { RowContainer, StyledCollapsiblePanel, } from './StyledComponent'; +import { GroupWithRows, ValueByField } from './types'; interface CheckboxSubTreeProps { group: GroupWithRows; diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx index 9c4cfc9f1..f26730d49 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -12,15 +12,12 @@ import { import CheckboxSubTree from './CheckboxSubTree'; import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; import { MODE_CREATE } from '../../constants/modes'; -import { CheckboxTreeProps, ValueByField } from './types'; +import { CheckboxTreeProps, SearchChangeData, ValueByField } from './types'; import { packValue, parseValue } from './utils'; - -type SearchChangeData = { - value: string; -}; +import { checkValidationForRequired } from './validation'; function CheckboxTree(props: CheckboxTreeProps) { - const { field, handleChange, controlOptions, disabled } = props; + const { field, handleChange, controlOptions, disabled, required } = props; const flattenedRowsWithGroups = getFlattenRowsWithGroups(controlOptions); const shouldUseDefaultValue = props.mode === MODE_CREATE && (props.value === null || props.value === undefined); @@ -30,6 +27,9 @@ function CheckboxTree(props: CheckboxTreeProps) { const [values, setValues] = useState(initialValues); const [searchForCheckBoxValue, setSearchForCheckBoxValue] = useState(''); + if (required) { + checkValidationForRequired(props.field, props.label, controlOptions.rows); + } // Propagate default values on mount if applicable useEffect(() => { diff --git a/ui/src/components/CheckboxTree/CheckboxTreeMocks.ts b/ui/src/components/CheckboxTree/CheckboxTreeMocks.ts new file mode 100644 index 000000000..41cc34a61 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTreeMocks.ts @@ -0,0 +1,26 @@ +import { http, HttpResponse } from 'msw'; + +export const serverHandlers = [ + http.get(`/servicesNS/:user/-/:serviceName`, () => + HttpResponse.json({ + entry: [ + { + name: 'name', + content: 'content', + id: 0, + }, + ], + }) + ), + http.post(`/servicesNS/:user/-/:serviceName`, () => + HttpResponse.json({ + entry: [ + { + name: 'name', + content: 'content', + id: 0, + }, + ], + }) + ), +]; diff --git a/ui/src/components/CheckboxTree/StyledComponent.tsx b/ui/src/components/CheckboxTree/StyledComponent.tsx index 546972d83..8550cf3ec 100644 --- a/ui/src/components/CheckboxTree/StyledComponent.tsx +++ b/ui/src/components/CheckboxTree/StyledComponent.tsx @@ -19,10 +19,13 @@ export const CheckboxContainer = styled.div` `; export const StyledCollapsiblePanel = styled(CollapsiblePanel)` + margin-top: ${variables.spacingXSmall}; & > *:not(:last-child) { - background-color: ${variables.neutral300}; + button { + background-color: ${variables.neutral300} !important; + } font-size: 14px; - margin-bottom: ${variables.spacingSmall}; + margin-bottom: ${variables.spacingXSmall}; } `; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx new file mode 100644 index 000000000..f4999a5b9 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx @@ -0,0 +1,214 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn, userEvent, within } from '@storybook/test'; +import CheckboxTree from '../CheckboxTree'; +import { MODE_CREATE, MODE_EDIT } from '../../../constants/modes'; + +const meta = { + component: CheckboxTree, + title: 'CheckboxTree/Component', +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Base: Story = { + args: { + handleChange: fn(), + mode: MODE_EDIT, + field: 'api', + value: 'collect_collaboration,collect_file,collect_task', + controlOptions: { + rows: [ + { + field: 'collect_collaboration', + checkbox: { + label: 'Collect folder collaboration', + }, + }, + { + field: 'collect_file', + checkbox: { + label: 'Collect file metadata', + }, + }, + { + field: 'collect_task', + checkbox: { + label: 'Collect tasks and comments', + }, + }, + ], + }, + }, +}; + +export const Multiline: Story = { + args: { + handleChange: fn(), + mode: MODE_EDIT, + field: 'api', + value: 'neigh,like', + controlOptions: { + rows: [ + { + field: 'like', + checkbox: { + label: 'I like ponies', + }, + }, + { + field: 'unicorn', + checkbox: { + label: 'Enable unicorn mode (Warning: May attract nearby ponies)', + }, + }, + { + field: 'neigh', + checkbox: { + label: "I agree to occasionally neigh like a pony when nobody's watching", + }, + }, + ], + }, + }, +}; + +export const WithSingleGroup: Story = { + args: { + ...Base.args, + value: undefined, + controlOptions: { + groups: [ + { + label: 'Group 1', + fields: ['collect_collaboration', 'collect_file'], + options: { isExpandable: false }, + }, + ], + rows: [ + { + field: 'collect_collaboration', + checkbox: { + label: 'Collect folder collaboration', + }, + }, + { + field: 'collect_file', + checkbox: { + label: 'Collect file metadata', + }, + }, + ], + }, + }, +}; + +export const MixedWithGroups: Story = { + args: { + ...Base.args, + value: 'collect_collaboration', + controlOptions: { + groups: [ + { + label: 'Expandable group', + fields: ['collect_collaboration', 'collect_file'], + options: { isExpandable: true, expand: true }, + }, + + { + label: 'Non expandable group', + fields: ['collect_folder_metadata'], + options: { isExpandable: false }, + }, + ], + rows: [ + { + field: 'collect_collaboration', + checkbox: { + label: 'Collect folder collaboration', + }, + }, + { + field: 'collect_file', + checkbox: { + label: 'Collect file metadata', + }, + }, + { + field: 'collect_task', + checkbox: { + label: 'Collect tasks and comments', + }, + }, + { + field: 'collect_folder_metadata', + checkbox: { + label: 'Collect folder metadata', + }, + }, + ], + }, + }, +}; + +export const CreateMode: Story = { + args: { + ...Base.args, + value: undefined, + mode: MODE_CREATE, + controlOptions: { + rows: [ + { + field: 'field1', + checkbox: { + label: 'checkbox list with default value true', + defaultValue: true, + }, + }, + { + field: 'field2', + checkbox: { + label: 'checkbox list with default value false', + defaultValue: false, + }, + }, + ], + }, + }, +}; + +export const Search: Story = { + args: { + ...Base.args, + value: undefined, + controlOptions: { + groups: [ + { + label: 'Group 1', + fields: ['collect_collaboration', 'collect_file'], + options: { isExpandable: false }, + }, + ], + rows: [ + { + field: 'collect_collaboration', + checkbox: { + label: 'Collect folder collaboration', + }, + }, + { + field: 'collect_file', + checkbox: { + label: 'Collect file metadata', + }, + }, + ], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const searchInput = canvas.getByRole('searchbox'); + const searchText = 'file'; + await userEvent.type(searchInput, searchText, { delay: 100 }); + }, +}; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeInputPage.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTreeInputPage.stories.tsx new file mode 100644 index 000000000..dd109196c --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeInputPage.stories.tsx @@ -0,0 +1,57 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { within, userEvent, expect } from '@storybook/test'; +import BaseFormView from '../../BaseFormView/BaseFormView'; +import { setUnifiedConfig } from '../../../util/util'; +import { serverHandlers } from '../CheckboxTreeMocks'; +import CheckboxTreeConfig from './CheckboxTreeMocks.json'; +import CheckboxTreeRequiredConfig from './CheckboxTreeRequiredMocks.json'; +import InputPage from '../../../pages/Input/InputPage'; + +const meta = { + component: InputPage, + title: 'CheckboxTree/Page', + render: (args) => { + setUnifiedConfig(args.globalConfig); + return ; + }, + args: { + globalConfig: CheckboxTreeConfig, + }, + parameters: { + msw: { + handlers: serverHandlers, + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const newInputBtn = canvas.getByRole('button', { name: 'Create New Input' }); + await userEvent.click(newInputBtn); + + const root = within(canvasElement.ownerDocument.body); + await expect(await root.findByRole('dialog')).toBeVisible(); + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const InputPageView: Story = {}; + +export const RequiredView: Story = { + args: { + globalConfig: CheckboxTreeRequiredConfig, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const newInputBtn = canvas.getByRole('button', { name: 'Create New Input' }); + await userEvent.click(newInputBtn); + + const root = within(canvasElement.ownerDocument.body); + await expect(await root.findByRole('dialog')).toBeVisible(); + + await userEvent.click(await root.findByText('Add')); + }, +}; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json new file mode 100644 index 000000000..f969e5add --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json @@ -0,0 +1,101 @@ +{ + "meta": { + "name": "Splunk_TA_aws", + "displayName": "Splunk Add-on for AWS", + "version": "7.1.0", + "restRoot": "restRoot", + "schemaVersion": "0.0.3" + }, + "pages": { + "configuration": { + "title": "", + "tabs": [ + { + "name": "a", + "title": "", + "entity": [] + } + ] + }, + "inputs": { + "title": "Inputs", + "table": { + "header": [ + { + "field": "name", + "label": "Input Name" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + } + ], + "actions": ["edit", "delete", "search", "clone"] + }, + "services": [ + { + "name": "example_input_four", + "title": "Title example", + "entity": [ + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "api3", + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["requiredField", "160validation"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "requiredField", + "checkbox": { + "label": "Required field", + "defaultValue": true + } + }, + { + "field": "160validation", + "checkbox": { + "label": "from 1 to 60 validation" + } + } + ] + } + } + ] + } + ] + } + } +} diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json new file mode 100644 index 000000000..514a75400 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json @@ -0,0 +1,102 @@ +{ + "meta": { + "name": "Splunk_TA_aws", + "displayName": "Splunk Add-on for AWS", + "version": "7.1.0", + "restRoot": "restRoot", + "schemaVersion": "0.0.3" + }, + "pages": { + "configuration": { + "title": "", + "tabs": [ + { + "name": "a", + "title": "", + "entity": [] + } + ] + }, + "inputs": { + "title": "Inputs", + "table": { + "header": [ + { + "field": "name", + "label": "Input Name" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + } + ], + "actions": ["edit", "delete", "search", "clone"] + }, + "services": [ + { + "name": "example_input_four", + "title": "Title example", + "entity": [ + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "api3", + "required": true, + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["requiredField", "160validation"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": false + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": false + } + }, + { + "field": "requiredField", + "checkbox": { + "label": "Required field", + "defaultValue": false + } + }, + { + "field": "160validation", + "checkbox": { + "label": "from 1 to 60 validation" + } + } + ] + } + } + ] + } + ] + } + } +} diff --git a/ui/src/components/CheckboxTree/types.ts b/ui/src/components/CheckboxTree/types.ts index bbb6b151d..e22ae0677 100644 --- a/ui/src/components/CheckboxTree/types.ts +++ b/ui/src/components/CheckboxTree/types.ts @@ -31,6 +31,8 @@ export type GroupWithRows = Group & { rows: Row[] }; export interface CheckboxTreeProps { field: string; value?: string; + required?: boolean; + label: string; controlOptions: { groups?: Group[]; rows: Row[]; @@ -43,3 +45,7 @@ export interface CheckboxTreeProps { handleChange: (field: string, value: string, componentType?: 'CheckboxTree') => void; disabled?: boolean; } + +export type SearchChangeData = { + value: string; +}; diff --git a/ui/src/components/CheckboxTree/utils.ts b/ui/src/components/CheckboxTree/utils.ts index 90d41d279..ed4203612 100644 --- a/ui/src/components/CheckboxTree/utils.ts +++ b/ui/src/components/CheckboxTree/utils.ts @@ -1,27 +1,11 @@ import { ValueByField, Field, Value } from './types'; export function parseValue(collection?: string): ValueByField { - const resultMap = new Map(); - if (!collection) { - return resultMap; + return new Map(); } - const splitValues = collection.split(','); - splitValues.forEach((rawValue) => { - const [field, inputValue] = rawValue.trim().split('/'); - const parsedInputValue = inputValue === '' ? undefined : Number(inputValue); - if (!field || Number.isNaN(parsedInputValue)) { - throw new Error(`Value is not parsable: ${collection}`); - } - - resultMap.set(field, { - checkbox: true, - inputValue: parsedInputValue, - }); - }); - - return resultMap; + return new Map(collection.split(',').map((rawValue) => [rawValue.trim(), { checkbox: true }])); } export function packValue(map: ValueByField): string { diff --git a/ui/src/components/CheckboxTree/validation.ts b/ui/src/components/CheckboxTree/validation.ts new file mode 100644 index 000000000..5c5121d58 --- /dev/null +++ b/ui/src/components/CheckboxTree/validation.ts @@ -0,0 +1,26 @@ +import Validator from '../../util/Validator'; +import { Row } from './types'; + +type MaybeError = + | { + errorField: string; + errorMsg: string; + } + | false; + +/** + * Validate the required field has value + * @param {string} field + * @param {string} label + * @param {object} [rows] + * @returns {Error|false} + */ + +export function checkValidationForRequired(field: string, label: string, rows: Row[]): MaybeError { + let errorMessage: MaybeError = false; + if (!rows.some((row) => row.checkbox?.defaultValue === true)) { + errorMessage = Validator.RequiredValidator(field, label, ''); + return errorMessage; + } + return false; +} diff --git a/ui/src/constants/ControlTypeMap.ts b/ui/src/constants/ControlTypeMap.ts index 995dcb540..bf4d95d8c 100644 --- a/ui/src/constants/ControlTypeMap.ts +++ b/ui/src/constants/ControlTypeMap.ts @@ -1,3 +1,4 @@ +import CheckboxTree from '../components/CheckboxTree/CheckboxTree'; import HelpLinkComponent from '../components/HelpLinkComponent/HelpLinkComponent'; import TextComponent from '../components/TextComponent/TextComponent'; import TextAreaComponent from '../components/TextAreaComponent/TextAreaComponent'; @@ -12,6 +13,7 @@ import CheckboxGroup from '../components/CheckboxGroup/CheckboxGroup'; const componentsMap = { checkbox: CheckBoxComponent, checkboxGroup: CheckboxGroup, + CheckboxTree, custom: CustomControl, file: FileInputComponent, helpLink: HelpLinkComponent, diff --git a/ui/src/types/globalConfig/entities.ts b/ui/src/types/globalConfig/entities.ts index 30661c6a7..4e335e9d5 100644 --- a/ui/src/types/globalConfig/entities.ts +++ b/ui/src/types/globalConfig/entities.ts @@ -220,6 +220,39 @@ export const CheckboxGroupEntity = CommonEditableEntityFields.extend({ }), }); +export const CheckboxTreeEntity = CommonEditableEntityFields.extend({ + type: z.literal('CheckboxTree'), + validators: z.tuple([RegexValidator]).optional(), + defaultValue: z.union([z.number(), z.boolean()]).optional(), + options: CommonEditableEntityOptions.extend({ + groups: z + .array( + z.object({ + label: z.string(), + fields: z.array(z.string()), + options: z + .object({ + isExpandable: z.boolean().optional(), + expand: z.boolean().optional(), + }) + .optional(), + }) + ) + .optional(), + rows: z.array( + z.object({ + field: z.string(), + checkbox: z + .object({ + label: z.string().optional(), + defaultValue: z.boolean().optional(), + }) + .optional(), + }) + ), + }), +}); + export const RadioEntity = CommonEditableEntityFields.extend({ type: z.literal('radio'), defaultValue: z.string().optional(), @@ -308,6 +341,7 @@ export const AnyOfEntity = z.discriminatedUnion('type', [ MultipleSelectEntity, CheckboxEntity, CheckboxGroupEntity, + CheckboxTreeEntity, RadioEntity, FileEntity, OAuthEntity, From f23a4c0b82dd1526943d236b31c3c5c27e5523c5 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Wed, 4 Dec 2024 11:50:10 +0530 Subject: [PATCH 04/23] chore: remove unwanted files --- .coveragerc | 2 +- .github/workflows/build-test-release.yml | 3 +- .gitignore | 3 +- NOTICE | 684 +----------------- README.md | 3 +- docs/CHANGELOG.md | 24 + docs/additional_packaging.md | 4 +- docs/commands.md | 8 +- docs/index.md | 4 +- docs/inputs/index.md | 23 +- docs/quickstart.md | 8 +- poetry.lock | 8 +- pyproject.toml | 6 +- renovate.json | 19 +- splunk_add_on_ucc_framework/__init__.py | 2 +- splunk_add_on_ucc_framework/commands/build.py | 3 + splunk_add_on_ucc_framework/commands/init.py | 38 +- .../install_python_libraries.py | 32 +- splunk_add_on_ucc_framework/main.py | 24 + .../schema/schema.json | 11 + .../templates/Licenses/Apache License 2.0.txt | 0 .../templates/Licenses/MIT License.txt | 21 + ...PRE-RELEASE SOFTWARE LICENSE AGREEMENT.txt | 49 ++ .../templates/app.manifest.init-template | 13 +- splunk_add_on_ucc_framework/utils.py | 23 +- tests/smoke/test_ucc_build.py | 4 + tests/smoke/test_ucc_init.py | 15 +- .../package/LICENSES/MIT License.txt | 21 + .../package/app.manifest | 6 +- .../expected_log.json | 2 +- .../.appinspect_api.expect.yaml | 2 - .../LICENSES/Apache License 2.0.txt} | 0 .../package/LICENSE.txt | 0 .../package/LICENSES}/LICENSE.txt | 0 .../additional_packaging.py | 36 + .../globalConfig.json | 2 +- .../package/LICENSES/Apache License 2.0.txt | 208 ++++++ .../package/app.manifest | 2 +- .../LICENSES/Apache-2.0.txt | 1 - .../additional_packaging.py | 4 +- .../package/LICENSES/Apache License 2.0.txt | 1 + .../globalConfig.json | 273 ++++++- .../ui/test_configuration_page_account_tab.py | 7 +- tests/ui/test_input_page.py | 7 +- tests/unit/commands/test_init.py | 79 ++ tests/unit/test_global_config_validator.py | 12 + tests/unit/test_install_python_libraries.py | 53 +- tests/unit/test_main.py | 53 ++ tests/unit/test_utils.py | 57 ++ ...config_with_input_status_confirmation.json | 90 +++ ui/jest.polyfills.ts | 4 + .../ControlWrapper/ControlWrapper.tsx | 17 +- .../stories/ControlWrapper.stories.ts | 34 + ...rolWrapper-with-modifications-chromium.png | 3 + ...h-modifications-make-required-chromium.png | 3 + .../test/ControlWrapper.test.tsx | 129 ++++ .../DeleteModal/DeleteModal.test.tsx | 1 - .../MultiInputComponent.tsx | 10 +- .../SingleInputComponent.tsx | 12 +- ui/src/components/table/CustomTable.tsx | 3 + .../components/table/CustomTableControl.jsx | 139 ++-- ui/src/components/table/CustomTableRow.tsx | 46 +- ui/src/components/table/TableWrapper.tsx | 17 +- .../components/table/stories/configMockups.ts | 116 +++ .../components/table/stories/rowDataMockup.ts | 8 + .../tests/CustomTableRowConfirmation.test.tsx | 152 ++++ .../table/tests/TableExpansionRow.test.tsx | 2 +- .../table/tests/TableWrapper.test.tsx | 2 - ui/src/context/TableContext.tsx | 1 + ui/src/types/globalConfig/pages.ts | 4 + ui/src/util/{api.test.ts => api.test.tsx} | 44 +- ui/src/util/api.ts | 3 +- 72 files changed, 1820 insertions(+), 880 deletions(-) rename tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache-2.0.txt => splunk_add_on_ucc_framework/templates/Licenses/Apache License 2.0.txt (100%) create mode 100644 splunk_add_on_ucc_framework/templates/Licenses/MIT License.txt create mode 100644 splunk_add_on_ucc_framework/templates/Licenses/SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT.txt create mode 100644 tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSES/MIT License.txt rename tests/testdata/{test_addons/package_global_config_everything/LICENSES/Apache-2.0.txt => expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache License 2.0.txt} (100%) delete mode 100644 tests/testdata/test_addons/package_files_conflict_test/package/LICENSE.txt rename tests/testdata/{expected_addons/expected_addon_after_init/demo_addon_for_splunk/package => test_addons/package_files_conflict_test/package/LICENSES}/LICENSE.txt (100%) create mode 100644 tests/testdata/test_addons/package_global_config_everything/additional_packaging.py create mode 100644 tests/testdata/test_addons/package_global_config_everything/package/LICENSES/Apache License 2.0.txt delete mode 100644 tests/testdata/test_addons/package_global_config_everything_uccignore/LICENSES/Apache-2.0.txt create mode 100644 tests/testdata/test_addons/package_global_config_everything_uccignore/package/LICENSES/Apache License 2.0.txt create mode 100644 tests/unit/testdata/valid_config_with_input_status_confirmation.json create mode 100644 ui/src/components/ControlWrapper/stories/__images__/ControlWrapper-with-modifications-chromium.png create mode 100644 ui/src/components/ControlWrapper/stories/__images__/ControlWrapper-with-modifications-make-required-chromium.png create mode 100644 ui/src/components/ControlWrapper/test/ControlWrapper.test.tsx create mode 100644 ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx rename ui/src/util/{api.test.ts => api.test.tsx} (62%) diff --git a/.coveragerc b/.coveragerc index 3be3f434a..e6d50447c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,4 +6,4 @@ omit = splunk_add_on_ucc_framework/templates/input.module-template [report] -fail_under = 81 +fail_under = 81.5 diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 495d32f30..0afc0b68d 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -325,8 +325,7 @@ jobs: needs: build-test-addon if: | !cancelled() && - needs.build-test-addon.result == 'success' && - ( github.base_ref == 'main' || github.ref_name == 'main' ) + needs.build-test-addon.result == 'success' runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index 303e93381..1f5efe983 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,9 @@ site/ assets/ Splunk_TA_Dynatrace/* Splunk_TA_Dynatrace_ucc/* -demo_addon_for_splunk/ demo_addon_for_splunk_already_exists/ +demo_addon_for_splunk_license/ +addon_name init_addon_for_ucc_package/ # UI build diff --git a/NOTICE b/NOTICE index a0c1673d6..39f6003a3 100644 --- a/NOTICE +++ b/NOTICE @@ -7,9 +7,9 @@ The following 3rd-party software packages may be used by or distributed with addonfactory-ucc-generator. Any information relevant to third-party vendors listed below are collected using common, reasonable means. -Date generated: 2024-11-18 +Date generated: 2024-11-27 -Revision ID: b59b29993757676871905494386cd9de0c4dfcd1 +Revision ID: 4f336c06c9bcec855df9035ba02fee26ad3296f7 ================================================================================ ================================================================================ @@ -7290,60 +7290,6 @@ Apache-2.0 -* Other Licenses * -BSD-3-Clause, MIT - -* BSD-3-Clause * - -Copyright (c) 2007 present, Alexandru Mărășteanu . All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -* MIT * - -Copyright (c) 2018 Jed Watson -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -------------------------------------------------------------------------------- Package Title: @splunk/visualization-color-palettes (26.4.1) @@ -13508,9 +13454,8 @@ Package Depth: Direct -------------------------------------------------------------------------------- * Declared Licenses * -PSF-2.0, Python-2.0 +PSF-2.0 -* PSF-2.0 * PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- @@ -13563,36 +13508,6 @@ Agreement. -* Python-2.0 * - -1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. - 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. - 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. - 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. - 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. - 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). - 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. - 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. - 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. - 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6, beta 1 software in source or binary form and its associated documentation, as released at the www.python.org Internet site on August 4, 2000 ("Python 1.6b1"). - 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6b1 alone or in any derivative version, provided, however, that CNRIs License Agreement is retained in Python 1.6b1, alone or in any derivative version prepared by Licensee. - Alternately, in lieu of CNRIs License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6, beta 1, is made available subject to the terms and conditions in CNRIs License Agreement. This Agreement may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1011. This Agreement may also be obtained from a proxy server on the Internet using the URL:http://hdl.handle.net/1895.22/1011". - 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6b1 or any part thereof, and wants to make the derivative work available to the public as provided herein, then Licensee hereby agrees to indicate in any such work the nature of the modifications made to Python 1.6b1. - 4. CNRI is making Python 1.6b1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6b1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING PYTHON 1.6b1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. - 7. This License Agreement shall be governed by and interpreted in all respects by the law of the State of Virginia, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. - 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6b1, Licensee agrees to be bound by the terms and conditions of this License Agreement. -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. -Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -------------------------------------------------------------------------------- Package Title: dequal (2.0.3) @@ -19848,292 +19763,8 @@ Package Depth: Transitive -------------------------------------------------------------------------------- * Declared Licenses * -0BSD, Python-2.0 - -* 0BSD * - -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations, which became -Zope Corporation. In 2001, the Python Software Foundation (PSF, see -https://www.python.org/psf/) was formed, a non-profit organization -created specifically to own Python-related Intellectual Property. -Zope Corporation was a sponsoring member of the PSF. - -All Python releases are Open Source (see https://opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -Python software and documentation are licensed under the -Python Software Foundation License Version 2. - -Starting with Python 3.8.6, examples, recipes, and other code in -the documentation are dual licensed under the PSF License Version 2 -and the Zero-Clause BSD license. +Python-2.0 -Some software incorporated into Python is under different licenses. -The licenses are listed with code falling under that license. - - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION ----------------------------------------------------------------------- - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - -* Python-2.0 * 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. @@ -22236,289 +21867,6 @@ Copyright (C) 2006 by Rob Landley Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -* 0BSD * - -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations, which became -Zope Corporation. In 2001, the Python Software Foundation (PSF, see -https://www.python.org/psf/) was formed, a non-profit organization -created specifically to own Python-related Intellectual Property. -Zope Corporation was a sponsoring member of the PSF. - -All Python releases are Open Source (see https://opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -Python software and documentation are licensed under the -Python Software Foundation License Version 2. - -Starting with Python 3.8.6, examples, recipes, and other code in -the documentation are dual licensed under the PSF License Version 2 -and the Zero-Clause BSD license. - -Some software incorporated into Python is under different licenses. -The licenses are listed with code falling under that license. - - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION ----------------------------------------------------------------------- - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - * APAFML * Copyright (c) 1985, 1987, 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved. @@ -28055,16 +27403,6 @@ BSD Zero Clause License Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation. - -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 - -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 - -Copyright (c) i.e., "Copyright (c) - -Copyright (c) - -Copyright (c) 1991 - 1995 Stichting Mathematisch Centrum Amsterdam, Copyright (C) 2006 by Rob Landley Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. @@ -28236,8 +27574,6 @@ BSD 3-Clause "New" or "Revised" License Copyright (c) 2017, Tim Radvan -Copyright (c) 2007 present, Alexandru Mărășteanu - Copyright (c) 2011-2024 Gregor Aisch Copyright (c) 2010-2021 James Hall , https://github.com/MrRio/jsPDF @@ -28649,8 +27985,6 @@ Copyright (c) OpenJS Foundation and other contributors Copyright (c) 2014 Call-Em-All -Copyright (c) 2018 Jed Watson - Copyright (c) 2017 Zeno Rocha Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -28881,10 +28215,6 @@ Copyright (c) 2020 Thomas Grainger. Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Copyright (c) i.e., "Copyright (c) - -Copyright (c) i.e., "Copyright (c) - -Copyright (c) PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. @@ -28899,11 +28229,7 @@ PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------------------------------------------- Python License 2.0 -Copyright (c) 2013-2017 by Christian Heimes - Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 - -Copyright (c) 2013 by Christian Heimes PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. @@ -28969,4 +28295,4 @@ either expressed or implied, of the FreeBSD Project. -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -Report Generated by FOSSA on 2024-11-18 +Report Generated by FOSSA on 2024-11-27 diff --git a/README.md b/README.md index bf43d3868..f08026fc6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# splunk-add-on-ucc-framework +# UCC ![PyPI](https://img.shields.io/pypi/v/splunk-add-on-ucc-framework) ![Python](https://img.shields.io/pypi/pyversions/splunk-add-on-ucc-framework.svg) +![PyPI monthly downloads](https://img.shields.io/pypi/dm/splunk-add-on-ucc-framework) ## What is UCC? diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8a99d4b1a..4495116c0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +# [5.54.0](https://github.com/splunk/addonfactory-ucc-generator/compare/v5.53.2...v5.54.0) (2024-11-27) + + +### Bug Fixes + +* allow packaging from 23.0 ([#1484](https://github.com/splunk/addonfactory-ucc-generator/issues/1484)) ([29accc4](https://github.com/splunk/addonfactory-ucc-generator/commit/29accc40a6c560f0181473627d0552a53af878b4)) +* display newest information in row more info section ([#1445](https://github.com/splunk/addonfactory-ucc-generator/issues/1445)) ([2b34c6b](https://github.com/splunk/addonfactory-ucc-generator/commit/2b34c6b226619d0cfed6582a35181b7705de3983)), closes [#1410](https://github.com/splunk/addonfactory-ucc-generator/issues/1410) +* required star visibility when using modify prop ([#1489](https://github.com/splunk/addonfactory-ucc-generator/issues/1489)) ([e1fe2b0](https://github.com/splunk/addonfactory-ucc-generator/commit/e1fe2b0b8322c5c49745ee3cc93cf538142325e0)) +* support Windows when checking library version ([#1482](https://github.com/splunk/addonfactory-ucc-generator/issues/1482)) ([db17b5c](https://github.com/splunk/addonfactory-ucc-generator/commit/db17b5c869b174639b0c24e5413bc9b517950940)) + + +### Features + +* add license during init command ([#1475](https://github.com/splunk/addonfactory-ucc-generator/issues/1475)) ([471294a](https://github.com/splunk/addonfactory-ucc-generator/commit/471294ae8e4c266a2f685aa4c01eb75fd1974db1)) +* confirmation modal when activate/deactivate single input ([#1421](https://github.com/splunk/addonfactory-ucc-generator/issues/1421)) ([34c8ec2](https://github.com/splunk/addonfactory-ucc-generator/commit/34c8ec250861eb06bd1cd4b22b430e5aa7e26a7c)) +* do not create `__pycache__` in lib dir ([#1469](https://github.com/splunk/addonfactory-ucc-generator/issues/1469)) ([ad58e50](https://github.com/splunk/addonfactory-ucc-generator/commit/ad58e50ca2b5588f6824a4da95a15e8c0857f032)) +* **inputs:** show input services status count ([#1430](https://github.com/splunk/addonfactory-ucc-generator/issues/1430)) ([2574451](https://github.com/splunk/addonfactory-ucc-generator/commit/257445159898a2207cdf7a397345c218678c8fcb)) + +## [5.53.2](https://github.com/splunk/addonfactory-ucc-generator/compare/v5.53.1...v5.53.2) (2024-11-21) + + +### Bug Fixes + +* **api:** cancelled requests don't emit user facing errors ([#1472](https://github.com/splunk/addonfactory-ucc-generator/issues/1472)) ([0970441](https://github.com/splunk/addonfactory-ucc-generator/commit/09704416b5a56ddb518eabd01d596ec5e23157e3)) ## [5.53.1](https://github.com/splunk/addonfactory-ucc-generator/compare/v5.53.0...v5.53.1) (2024-11-18) diff --git a/docs/additional_packaging.md b/docs/additional_packaging.md index ea63cead0..1a3c7c4d4 100644 --- a/docs/additional_packaging.md +++ b/docs/additional_packaging.md @@ -2,7 +2,7 @@ To extend the build process, you can create a `additional_packaging.py` file in the same file level where you have your globalConfig file. -This file should at least have: +This file should have either of the below two functions: - the `cleanup_output_files` function, which accepts `output_path` (str), `add-on name` (str) as its arguments. - the `additional_packaging` function, which accepts `add-on name` (str) as its only argument. @@ -19,5 +19,5 @@ See the following example for proper usage: Below is an example of additional_packaging.py containing both the implementations of functions. ```python ---8<-- "tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py" +--8<-- "tests/testdata/test_addons/package_global_config_everything/additional_packaging.py" ``` diff --git a/docs/commands.md b/docs/commands.md index 8e2c2c28c..4587d6faa 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -74,8 +74,12 @@ It takes the following parameters: * `--addon-display-name` - [required] add-on "official" name. * `--addon-input-name` - [required] name of the generated input. * `--addon-version` - [optional] version of the generated add-on, with `0.0.1` by default. -* `--overwrite` - [optional] overwrites the already existing folder. - By default, you can't generate a new add-on to an already existing folder. +* `--overwrite` - [optional] overwrites the already existing folder. By default, you can't generate a new add-on to an already existing folder. +* `--add-license` - [optional] Adds license agreement such as [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt), [MIT License](https://mit-license.org/), or +[SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT](https://www.splunk.com/en_us/legal/splunk-pre-release-software-license-agreement.html) in your `package/LICENSES` directory. If not mentioned an empty License.txt will be generated. +* `--include-author` - [optional] Allows you to specify the author of the add-on during initialization. The author's name will appear in `app.manifest` under `info -> author -> name` and in `app.conf` (after building your add-on) under `launcher -> author` field. + +> **Note:** The add-on will not build if the input for `--add-license` is not one of the following: `Apache License 2.0`, `MIT License`, or `SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT`. If you want to keep another license in your add-on, place it in `package/LICENSES` directory and it will be shipped ## `ucc-gen import-from-aob` diff --git a/docs/index.md b/docs/index.md index 194f2f627..88532b402 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,10 +4,10 @@ Universal Configuration Console (UCC) is a framework that simplifies the process The UCC framework helps you to maintain consistency and a uniform look and feel across different add-ons. You can easily update and modify your add-ons. -The UCC framework is available as a GitHub action. See . - To work with UCC framework, you can also use Splunk Extension. It helps you to create, test, and debug the add-ons in a simple way. For more information, see [Visual Studio Code Extension for Splunk](https://marketplace.visualstudio.com/items?itemName=Splunk.splunk). +To see how UCC can be used in an add-on, see [Example TA](https://github.com/splunk/splunk-example-ta). + ## Libraries UCC-based add-ons are powered by the following Splunk libraries: diff --git a/docs/inputs/index.md b/docs/inputs/index.md index cd32f4b78..7abceb562 100644 --- a/docs/inputs/index.md +++ b/docs/inputs/index.md @@ -11,17 +11,18 @@ provided, a dropdown field will appear on the Inputs page. In contrast, a button ### Properties -| Property | Type | Description | -| ------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| title\* | string | - | -| description | string | It provides a brief summary of an inputs page. | -| [subDescription](../advanced/sub_description.md) | object | It provides broader description of an inputs page. | -| menu | object | This property allows you to enable the [custom menu](../custom_ui_extensions/custom_menu.md) feature. | -| [table](../table.md) | object | It displays input stanzas in a tabular format. | -| groupsMenu | array | This property allows you to enable the [multi-level menu](./multilevel_menu.md) feature. | -| [services](#services-properties)\* | array | It specifies a list of modular inputs. | -| readonlyFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input cannot be edited from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. | -| hideFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input is hidden from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. Check out an example below. | +| Property | Type | Description | +| ------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| title\* | string | - | +| description | string | It provides a brief summary of an inputs page. | +| [subDescription](../advanced/sub_description.md) | object | It provides broader description of an inputs page. | +| menu | object | This property allows you to enable the [custom menu](../custom_ui_extensions/custom_menu.md) feature. | +| [table](../table.md) | object | It displays input stanzas in a tabular format. | +| groupsMenu | array | This property allows you to enable the [multi-level menu](./multilevel_menu.md) feature. | +| [services](#services-properties)\* | array | It specifies a list of modular inputs. | +| readonlyFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input cannot be edited from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. | +| hideFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input is hidden from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. Check out an example below. | +| useInputToggleConfirmation | boolean | When true, displays a confirmation modal before toggling an input's status between active and inactive. | ### Services Properties diff --git a/docs/quickstart.md b/docs/quickstart.md index 917028ebf..02c25370f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -22,7 +22,7 @@ Depending on which operating system you use, follow one of the procedures: Set up the Python virtual environment: ```bash - python3 -m venv .venv +python3 -m venv .venv ``` If you use cmd.exe, activate the virtual environment with the following command: @@ -42,8 +42,8 @@ If you use PowerShell, activate the virtual environment with the following comma Set up and activate the Python virtual environment: ```bash - python3 -m venv .venv - source .venv/bin/activate +python3 -m venv .venv +source .venv/bin/activate ``` ### Install UCC package @@ -75,7 +75,7 @@ ucc-gen build --source demo_addon_for_splunk/package ### Package the add-on ```bash -ucc-gen package --path output/ +ucc-gen package --path output/demo_addon_for_splunk ``` The archive is created on the same level as your `globalConfig.json` file. diff --git a/poetry.lock b/poetry.lock index 138e67e22..bcd31c78f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1283,13 +1283,13 @@ xmltodict = ">=0.13.0,<0.14.0" [[package]] name = "pytest-splunk-addon-ui-smartx" -version = "5.3.0" +version = "5.3.1" description = "Library to support testing Splunk Add-on UX" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "pytest_splunk_addon_ui_smartx-5.3.0-py3-none-any.whl", hash = "sha256:bd3b646469d869757d9fef4be9129fa373265121f027af21729828aa38726392"}, - {file = "pytest_splunk_addon_ui_smartx-5.3.0.tar.gz", hash = "sha256:0b684382320aaf18b0047fd61a32fc9df232bd06d8b2b47b3716a65236390b26"}, + {file = "pytest_splunk_addon_ui_smartx-5.3.1-py3-none-any.whl", hash = "sha256:a7a095e8b0f61f212ab569ec67e5ab4e44dc9e77acf05e3f01c9cacd99f959fe"}, + {file = "pytest_splunk_addon_ui_smartx-5.3.1.tar.gz", hash = "sha256:eacb9cf725bb4bbfe6646eee3bbed3a585b6ea190879d9ed5afc5db784e1b0aa"}, ] [package.dependencies] @@ -1782,4 +1782,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7,<3.13" -content-hash = "0435209576291a24a097f17bcf32f89216d592fb01e88fe72c521cb7379ff474" +content-hash = "ec35c6372a95df5d2d6e94ae22b949438cbcddfb05737c643c6990a79a32e140" diff --git a/pyproject.toml b/pyproject.toml index 0cbce9dc9..4395927c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ [tool.poetry] name = "splunk_add_on_ucc_framework" -version = "5.53.1" +version = "5.54.0" description = "Splunk Add-on SDK formerly UCC is a build and code generation framework" license = "Apache-2.0" authors = ["Splunk "] @@ -53,14 +53,14 @@ jsonschema = "^4.4.0" PyYAML = "^6.0" defusedxml = "^0.7.1" colorama = "^0.4.6" -packaging = "24.0" +packaging = ">=23.0" [tool.poetry.group.dev.dependencies] mkdocs = "^1.4.2" importlib-metadata = {version="*", python="<3.8"} pytest = "^7.2.1" pytest-splunk-addon = "^5.4.0" -pytest-splunk-addon-ui-smartx = "^5.3.0" +pytest-splunk-addon-ui-smartx = "^5.3.1" pytest-rerunfailures = "^11.1.1" mkdocs-material = "^9.1.3" mkdocstrings = {version=">=0", extras=["python"]} diff --git a/renovate.json b/renovate.json index 180f5c14f..f0a217688 100644 --- a/renovate.json +++ b/renovate.json @@ -24,7 +24,7 @@ "matchManagers": [ "poetry" ], - "matchPackagePrefixes": [ + "matchPackageNames": [ "importlib-metadata", "pytest", "pytest-cov", @@ -35,6 +35,23 @@ ], "enabled": false }, + { + "description": "Ignore Python version updates", + "matchManagers": ["poetry"], + "matchPackageNames": ["python"], + "enabled": false + }, + { + "description": "Ignore mkdocs and its plugins that drop Python 3.7 support", + "matchPackageNames": [ + "/^mkdocs" + ], + "matchUpdateTypes": [ + "major", + "minor" + ], + "enabled": false + }, { "matchPackageNames": ["urllib3"], "allowedVersions": "<2.0.0" diff --git a/splunk_add_on_ucc_framework/__init__.py b/splunk_add_on_ucc_framework/__init__.py index 3db106799..50dd034de 100644 --- a/splunk_add_on_ucc_framework/__init__.py +++ b/splunk_add_on_ucc_framework/__init__.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "5.53.1" +__version__ = "5.54.0" import logging diff --git a/splunk_add_on_ucc_framework/commands/build.py b/splunk_add_on_ucc_framework/commands/build.py index b6d94c9f4..2f547c638 100644 --- a/splunk_add_on_ucc_framework/commands/build.py +++ b/splunk_add_on_ucc_framework/commands/build.py @@ -582,6 +582,7 @@ def generate( removed_list = _remove_listed_files(ignore_list) if removed_list: logger.info("Removed:\n{}".format("\n".join(removed_list))) + utils.check_author_name(source, app_manifest) utils.recursive_overwrite(source, os.path.join(output_directory, ta_name)) logger.info("Copied package directory") @@ -655,6 +656,8 @@ def generate( "additional_packaging.py is present but does not have `additional_packaging`. " "Skipping additional packaging." ) + # clean-up sys.path manipulation + sys.path.pop(0) if global_config: logger.info("Generating OpenAPI file") diff --git a/splunk_add_on_ucc_framework/commands/init.py b/splunk_add_on_ucc_framework/commands/init.py index acdd4564a..d503aeca4 100644 --- a/splunk_add_on_ucc_framework/commands/init.py +++ b/splunk_add_on_ucc_framework/commands/init.py @@ -67,6 +67,8 @@ def _generate_addon( addon_version: str, addon_rest_root: str | None = None, overwrite: bool = False, + add_license: str | None = None, + include_author: str | None = None, ) -> str: generated_addon_path = os.path.join( os.getcwd(), @@ -103,9 +105,21 @@ def _generate_addon( _f.write(global_config_rendered_content) package_path = os.path.join(generated_addon_path, "package") os.makedirs(package_path) - package_license_path = os.path.join(package_path, "LICENSE.txt") - with open(package_license_path, "w") as _f: - pass + package_license_dir = os.path.join(package_path, "LICENSES") + os.makedirs(package_license_dir) + + if add_license: + package_license_path = os.path.join(package_license_dir, f"{add_license}.txt") + template_path = utils.get_license_path(add_license) + with open(template_path) as content: + license_content = content.read() + with open(package_license_path, "w") as _f: + _f.write(license_content) + else: + # add-on licenses are to be kept in package/LICENSES directory only + package_license_path = os.path.join(package_license_dir, "LICENSE.txt") + with open(package_license_path, "w") as _f: + pass package_readme_path = os.path.join(package_path, "README.txt") with open(package_readme_path, "w") as _f: pass @@ -117,6 +131,8 @@ def _generate_addon( addon_name=addon_name, addon_version=addon_version, addon_display_name=addon_display_name, + add_license=add_license, + include_author=include_author, ) ) with open(package_app_manifest_path, "w") as _f: @@ -160,6 +176,8 @@ def init( addon_version: str, addon_rest_root: str | None = None, overwrite: bool = False, + add_license: str | None = None, + include_author: str | None = None, ) -> str: if not _is_valid_addon_name(addon_name): logger.error( @@ -192,6 +210,11 @@ def init( f"it should follow '{ADDON_INPUT_NAME_RE_STR}' regex and be less than 50 characters." ) sys.exit(1) + if include_author == "": + logger.error("The author name cannot be left empty, please provide some input.") + sys.exit(1) + if include_author: + include_author = include_author.strip() generated_addon_path = _generate_addon( addon_name, addon_display_name, @@ -199,12 +222,13 @@ def init( addon_version, addon_rest_root, overwrite, + add_license, + include_author, ) logger.info(f"Generated add-on is located here {generated_addon_path}") - logger.info( - "LICENSE.txt and README.txt are empty, " - "you may need to modify the content of those files\n" - ) + if add_license: + logger.info(f"Specified {add_license} has been added to Licenses directory.") + logger.info("README.txt is empty, you may need to modify the content of this file.") logger.info( f"""You have generated your add-on and are wondering what to do next? \ UCC offers many solutions to improve add-ons! diff --git a/splunk_add_on_ucc_framework/install_python_libraries.py b/splunk_add_on_ucc_framework/install_python_libraries.py index fa5e00783..40ae0baea 100644 --- a/splunk_add_on_ucc_framework/install_python_libraries.py +++ b/splunk_add_on_ucc_framework/install_python_libraries.py @@ -95,25 +95,27 @@ def _pip_is_lib_installed( lib_installed_cmd = f"{installer} -m pip show --version {libname}" - if version and allow_higher_version: - cmd = f'{lib_installed_cmd} | grep "Version"' - elif version and not allow_higher_version: - cmd = f'{lib_installed_cmd} | grep "Version: {version}"' - else: - cmd = lib_installed_cmd - try: my_env = os.environ.copy() my_env["PYTHONPATH"] = target - if allow_higher_version: - result = _subprocess_run(command=cmd, env=my_env) - if result.returncode != 0: - return False - result_version = result.stdout.decode("utf-8").split("Version:")[1].strip() - return Version(result_version) >= Version(version) + + # Disable writing of .pyc files (__pycache__) + my_env["PYTHONDONTWRITEBYTECODE"] = "1" + + result = _subprocess_run(command=lib_installed_cmd, env=my_env) + if result.returncode != 0 or "Version:" not in result.stdout.decode("utf-8"): + return False + + if version: + pip_show_result = result.stdout.decode("utf-8").splitlines() + result_row = next(el for el in pip_show_result if el.startswith("Version:")) + result_version = result_row.split("Version:")[1].strip() + if allow_higher_version: + return Version(result_version) >= Version(version) + return Version(result_version) == Version(version) else: - return_code = _subprocess_run(command=cmd, env=my_env).returncode - return return_code == 0 + return result.returncode == 0 + except OSError as e: raise CouldNotInstallRequirements from e diff --git a/splunk_add_on_ucc_framework/main.py b/splunk_add_on_ucc_framework/main.py index 71c86af6e..6ea50c034 100644 --- a/splunk_add_on_ucc_framework/main.py +++ b/splunk_add_on_ucc_framework/main.py @@ -200,6 +200,28 @@ def main(argv: Optional[Sequence[str]] = None) -> int: default=False, help="overwrite already generated add-on folder", ) + init_parser.add_argument( + "--add-license", + type=str, + choices=[ + "Apache License 2.0", + "MIT License", + "SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT", + ], + help=( + "adds any one of license agreement such as 'Apache License 2.0', 'MIT License', or " + "'SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT' to the `/package/LICENSES` directory." + ), + required=False, + default=None, + ) + init_parser.add_argument( + "--include-author", + type=str, + help="adds author in app.mainifest under `info -> author -> name` field", + required=False, + default=None, + ) import_from_aob_parser = subparsers.add_parser( "import-from-aob", description="[Experimental] Import from AoB" @@ -235,6 +257,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int: addon_input_name=args.addon_input_name, addon_version=args.addon_version, overwrite=args.overwrite, + add_license=args.add_license, + include_author=args.include_author, ) if args.command == "import-from-aob": import_from_aob.import_from_aob( diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index 8bb77eb7f..0c6aebbcd 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -3227,6 +3227,9 @@ }, "readonlyFieldId": { "type": "string" + }, + "useInputToggleConfirmation": { + "$ref": "#/definitions/useInputToggleConfirmation" } }, "required": [ @@ -3274,6 +3277,9 @@ "table": { "$ref": "#/definitions/InputsTable" }, + "useInputToggleConfirmation": { + "$ref": "#/definitions/useInputToggleConfirmation" + }, "entity": { "$ref": "#/definitions/AnyOfEntity" }, @@ -3391,6 +3397,11 @@ "type": "string", "description": "Text displayed next to entity field" }, + "useInputToggleConfirmation": { + "type": "boolean", + "description": "When true displays additional confirmation modal when toggling single input status.", + "default": false + }, "IntervalEntity": { "type": "object", "properties": { diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache-2.0.txt b/splunk_add_on_ucc_framework/templates/Licenses/Apache License 2.0.txt similarity index 100% rename from tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache-2.0.txt rename to splunk_add_on_ucc_framework/templates/Licenses/Apache License 2.0.txt diff --git a/splunk_add_on_ucc_framework/templates/Licenses/MIT License.txt b/splunk_add_on_ucc_framework/templates/Licenses/MIT License.txt new file mode 100644 index 000000000..03e8bb5ab --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/Licenses/MIT License.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright [yyyy] [name of copyright owner] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/splunk_add_on_ucc_framework/templates/Licenses/SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT.txt b/splunk_add_on_ucc_framework/templates/Licenses/SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT.txt new file mode 100644 index 000000000..6a79e6de5 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/Licenses/SPLUNK PRE-RELEASE SOFTWARE LICENSE AGREEMENT.txt @@ -0,0 +1,49 @@ +THIS PRE-RELEASE SOFTWARE LICENSE AGREEMENT (THE "AGREEMENT") GOVERNS YOUR USE OF THE PRE-RELEASE SOFTWARE (DEFINED BELOW) PROVIDED BY SPLUNK LLC, AND/OR ITS AFFILIATES ("SPLUNK”). YOU WILL BE REQUIRED TO INDICATE YOUR AGREEMENT TO THIS AGREEMENT IN ORDER TO ACCESS OR DOWNLOAD THE PRE-RELEASE SOFTWARE OR TO COMPLETE THE INSTALLATION PROCESS FOR THE PRE-RELEASE SOFTWARE. BY CLICKING ON THE "YES" OR “I ACCEPT” BUTTON (OR OTHER BUTTON OR MECHANISM DESIGNED TO ACKNOWLEDGE AGREEMENT TO THE TERMS OF THIS AGREEMENT), OR BY DOWNLOADING, ACCESSING OR INSTALLING THE PRE-RELEASE SOFTWARE, YOU ARE CONSENTING TO BE BOUND BY THIS AGREEMENT. + +NOTE: THIS IS THE ONLY AGREEMENT BETWEEN YOU AND SPLUNK GOVERNING YOUR USE OF THE PRE-RELEASE SOFTWARE, AND THIS AGREEMENT SHALL SUPERSEDE ANY OTHER TERMS OF USE OR LICENSE AGREEMENT THAT MAY APPEAR DURING THE INSTALLATION OR DOWNLOADING OF THE PRE-RELEASE SOFTWARE. + +IF YOU AGREE TO THESE TERMS ON BEHALF OF A BUSINESS, YOU REPRESENT AND WARRANT THAT YOU HAVE THE POWER AND AUTHORITY TO BIND THAT BUSINESS TO THIS AGREEMENT, AND YOUR AGREEMENT TO THESE TERMS WILL BE TREATED AS THE AGREEMENT OF THE BUSINESS. IN THAT EVENT, "YOU" AND "YOUR" REFER HEREIN TO THAT BUSINESS. + +IF YOU ARE UNWILLING TO AGREE TO THIS AGREEMENT, OR YOU DO NOT HAVE THE RIGHT, POWER AND AUTHORITY TO ACT ON BEHALF OF AND BIND YOUR BUSINESS, DO NOT CLICK ON THE BUTTON AND DO NOT INSTALL, DOWNLOAD, ACCESS, OR OTHERWISE USE THE PRE-RELEASE SOFTWARE AND CANCEL THE LOADING OF THE PRE-RELEASE SOFTWARE. + +1. DEFINITIONS. + +“Pre-Release Software” means the pre-release version of the Splunk product, service, technology identified on the software download page or landing page or invitation message (the “Cover Page”), whether labeled as alpha, beta, pre-release, preview or otherwise, provided to you by Splunk under this Agreement. Pre-Release Software may include any enhancements, updates, upgrades, derivatives or bug fixes to such product, service or technology and any documentation, add-ons, templates, sample data sets or hardware devices as provided by Splunk. + +“Data” means the raw data you upload or submit to the Pre-Release Software and the processed result of the raw data generated by you using the Pre-Release Software. + +“Feedback” means all suggestions, comments, opinions, code, input, ideas, reports, information, know-how or other feedback provided by you (whether in oral, electronic or written form) to Splunk in connection with your use of the Pre-Release Software. Feedback does not include Data, unless submitted or communicated by you to Splunk as part of Feedback. + +“Internal Purposes” means internal business use with your systems, networks, devices and data for the purposes of internal testing and evaluation of the Pre-Release Software in order to provide Feedback to Splunk regarding the Pre-Release Software. Such use does not include use of your systems, networks or devices as part of services you provide for a third party's benefit. + +2. PRE-RELEASE SOFTWARE LICENSE. Subject to your compliance with the terms and conditions of this Agreement, Splunk grants you a non-exclusive, non-sublicensable, nontransferable, revocable, limited license during the term of the Agreement to use a single copy of the Pre-Release Software at your principal office in a secure location, only in connection with and solely for the Internal Purposes. + +3. LICENSE RESTRICTIONS. Except as expressly authorized in this Agreement or by Splunk, you will not, and will not permit any third party to: (i) access or use the Pre-Release Software for any other purposes than the Internal Purposes (including for any competitive analysis, commercial, professional, or other for-profit purposes); (ii) copy the Pre-Release Software (except as required to run the Pre-Release Software and for reasonable backup purposes); (iii) modify, adapt, or create derivative works of the Pre-Release Software; (iv) rent, lease, loan, resell, transfer, sublicense or distribute the Pre-Release Software to any third party; (v) use or offer any functionality of the Pre-Release Software on a service provider, service bureau, hosted, software as a service, or time sharing basis; (vi) decompile, disassemble or reverse-engineer the Pre-Release Software or otherwise attempt to derive the Pre-Release Software source code, algorithms, methods or techniques used or embodied in the Pre-Release Software; (vii) disclose to any third party the results of any benchmark tests or other evaluation of the Pre-Release Software, or (viii) remove, alter, obscure, cover or change any trademark, copyright or other proprietary notices, labels or markings from or on the Pre-Release Software; (ix) interfere with or disrupt servers or networks connected to any website through which the Pre-Release Software provided; or (x) use the Pre-Release Software to collect or store personal data about any person or entity. Any consultant, contractor, or agent hired to perform services for you may operate the Pre-Release Software on your behalf under these terms and conditions, provided that: (a) you are responsible for ensuring that any such third party agrees to abide by and fully comply with the terms of this Agreement on the same basis as applicable to you; (b) such use is only in connection with your Internal Purposes; (c) such use does not represent or constitute an increase in the scope of the licenses provided hereunder; and (d) you remain fully liable for any and all acts or omissions by such third parties related to this Agreement. Any violation of this Section shall be a material breach of this Agreement subject to immediate termination of this Agreement for which no notice from Splunk shall be required. + +4. CONFIDENTIALITY. You agree to hold the Pre-Release Software (including all intellectual property rights therein, such as any patents, inventions, copyrights, design rights, trade secrets and know-how) and any related information (“Confidential Information”), whether in oral or written form, confidential. Confidential Information may include information relating to features, functionalities, improvements, code, pricing, business strategies, product roadmaps, development plans, marketing materials, data sets, customer lists or other proprietary third-party information. You will hold such Confidential Information in strict confidence and not use or disclose the Confidential Information, in whole or in part, except as expressly permitted in this Agreement. You may disclose Confidential Information to your employees, but only to the extent they have a need to know to test the Pre-Release Software and you have advised them that such information is Confidential. You agree to instruct any such employees in advance who will have access to the Pre-Release Software that they must comply with the restrictions set forth in this Agreement. You shall have no obligation to maintain the confidentiality of any information which: (a) is or becomes publicly available without breach of this Agreement; (b) is rightfully received by you from a third party without an obligation of confidentiality and without breach of this Agreement; (c) is developed independently by you without access to or use of the Confidential Information; or (d) has been approved for release by written authorization of the party that owns the Confidential Information. You will notify immediately upon discovery of any unauthorized use or disclosure of Confidential Information or any other breach of this Agreement by you or your personnel, and will cooperate with Splunk in every reasonable way to help Splunk regain possession of the Confidential Information and prevent its further unauthorized use or disclosure. You acknowledge that any breach of its obligations under this Agreement with respect to the proprietary rights or Confidential Information will cause Splunk irreparable injury for which there are inadequate remedies at law, and therefore, Splunk will be entitled to equitable relief in addition to all other remedies provided by this Agreement or available at law or in equity. + +5. TERM AND TERMINATION. This Agreement will be effective from the earlier of (a) the date it is accepted by you and (b) the date on which you first installed, downloaded or accessed a copy of the Pre-Release Software and shall continue until terminated. This Agreement may be terminated at any time by either party, with or without cause, effective upon notice of termination. This Agreement will terminate automatically upon the end of the Pre-Release Software project as identified on the Cover Page or upon commercial release (if any) of the Pre-Release Software, whichever is earlier. This is time-sensitive software, so it will stop functioning on the termination date. Upon termination, you will immediately cease all use of the Pre-Release Software and destroy the Pre-Release Software, or upon request by Splunk, return to Splunk the Pre-Release Software and other Confidential Information that are in your possession or control. Upon Splunk’s request, you will certify in writing that you have returned or destroyed all copies of the Pre-Release Software and Confidential Information. Sections 1, 3, 4, 6, 7, 8, 9, 10, 11, 15, and 16 will survive termination of this Agreement. + +6. OWNERSHIP. Splunk, its suppliers and/or its licensors own all worldwide right, title and interest in and to the Pre-Release Software, including all worldwide patent rights (including patent applications and disclosures); copyright rights (including copyrights, copyright registration and copy rights with respect to computer software, software design, software code, software architecture, firmware, programming tools, graphic user interfaces, reports, dashboard, business rules, use cases, screens, alerts, notifications, drawings, specifications and databases); moral rights; trade secrets and other rights with respect to confidential or proprietary information; know-how; other rights with respect to inventions, discoveries, ideas, improvements, techniques, formulae, algorithms, processes, schematics, testing procedures, technical information and other technology; and any other intellectual and industrial property rights, whether or not subject to registration or protection; and all rights under any license or other arrangement with respect to the foregoing. Except as expressly stated in this Agreement, Splunk does not grant you any intellectual property rights in the Pre-Release Software, and all right, title, and interest in and to all copies of the Pre-Release Software not expressly granted remain with Splunk, its suppliers and/or its licensors. The Pre-Release Software is copyrighted and protected by the laws of the United States and other countries, and international treaty provisions. You acknowledge that the Pre-Release Software is licensed and not sold. + +7. FEEDBACK. Splunk, in its sole discretion, may or may not respond to your Feedback or promise to address all your Feedback in the development of future features or functionalities of the Pre-Release Software or any related or subsequent versions of such Pre-Release Software. In the event Splunk uses your Feedback, you grant Splunk an unrestricted, perpetual, worldwide, exclusive, transferable, irrevocable, sublicensable, royalty-free, fully paid-up license to use, copy, modify, create derivative works of, make, have made, distribute (through multiple tiers of distribution), publicly perform or display, import, export, sell, offer to sell, rent, or license copies of the Feedback as part of or in connection with any Splunk product, service, technology, content, material, specification or documentation. You warrant that the Feedback does not infringe any copyright or trade secret of any third party, and that you have no knowledge of any patent of any third party that may be infringed by the Feedback (including any implementation thereof recommended by you). You further warrant that your Feedback is not subject to any license terms that would purport to require Splunk to comply with any additional obligations with respect to any Splunk product or service that incorporates your Feedback. + +8. DATA. You hereby grant Splunk a perpetual, irrevocable, non-exclusive, royalty-free, paid-up, worldwide, sublicensable license to use, access, transmit, host, store, and display the Data solely for the purpose of providing and improving the Pre-Release Software. Splunk (or its sublicensees) may exercise such license for purposes of providing, maintaining, repairing, administering and improving the Pre-Release Software or in developing new products or services, including rights to extract, compile, aggregate, synthesize, use, and otherwise analyze all or any portion of the Data. You represent, warrant and agree that the Data and other materials you provide or make available to Splunk will include only information relevant to the Pre-Release Software and the use thereof and will not include any personally identifiable information or any protected health data. You acknowledge and agree that you are solely responsible for all Data you upload or submit using the Pre-Release Software and for your conduct while using the Pre-Release Software. You acknowledge and agree that: (a) you will evaluate and bear all risks associated with the use of any Data; (b) you are responsible for protecting and backing up the Data; (c) you are responsible for protecting the confidentiality of any Data; and (d) under no circumstances will Splunk be liable in any way for any Data, including, but not limited to, any errors or omissions in any Data, or any loss or damages or any kind incurred as a result of your use, deletion, modification, or correction of any Data. Splunk has no responsibility to store, protect, remove or delete any Data for you and shall have no liability for the deletion of or failure to store any Data. + +9. WARRANTY DISCLAIMER. THE PRE-RELEASE SOFTWARE IS PROVIDED “AS IS”. SPLUNK DISCLAIMS ANY AND ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, QUIET ENJOYMENT AND WARRANTIES ARISING OUT OF COURSE OF DEALING OR USAGE OF TRADE OR BY STATUTE OR IN LAW. SPLUNK SPECIFICALLY DOES NOT WARRANT THAT THE PRE-RELEASE SOFTWARE WILL MEET YOUR REQUIREMENTS, THE OPERATION OR OUTPUT OF THE PRE-RELEASE SOFTWARE WILL BE ERROR-FREE, ACCURATE, RELIABLE, COMPLETE OR UNINTERRUPTED. Splunk is not obligated to support, update or upgrade the Pre-Release Software. + +10. NO RELIANCE. Splunk has no obligations about any forward-looking statements made in connection with or in the course of providing the Pre-Release Software. Forward-looking statements are statements regarding future Splunk events, product offerings, product performance, customer uses or the expected financial performance of Splunk. Any such statements reflect current expectations and estimates based on factors currently known and that actual events or results could differ materially. Splunk does not assume any obligation to update any forward-looking statements made during the Pre-Release Software project. In addition, any information about our roadmap outlines our general product direction and is subject to change at any time without notice. It is for informational purposes only and shall not be incorporated into this Agreement or any contract or other commitment. Splunk undertakes no obligation either to develop the features or functionality described in the forward-looking statement or to include any such feature or functionality in a future release, including those you are reviewing as a part of this Pre-Release Software. You expressly acknowledge that the Pre-Release Software has not been fully tested and may contain defects or deficiencies which may not be corrected by Splunk, that the Pre-Release Software may undergo significant changes prior to release of the corresponding generally available final version. + +11. LIMITATION OF LIABILITY. IN NO EVENT WILL SPLUNK BE LIABLE TO YOU FOR ANY SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES (INCLUDING LOSS OF USE, DATA, OR PROFITS, BUSINESS INTERRUPTION, OR COSTS OF PROCURING SUBSTITUTE PRE-RELEASE SOFTWARE) ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT OR THE USE OR PERFORMANCE OF THE PRE-RELEASE SOFTWARE, WHETHER SUCH LIABILITY ARISES FROM CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, AND WHETHER OR NOT SPLUNK HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGE. THE PARTIES HAVE AGREED THAT THESE LIMITATIONS WILL SURVIVE AND APPLY EVEN IF ANY REMEDY IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. WITHOUT LIMITING THE FOREGOING, SPLUNK WILL HAVE NO LIABILITY OR RESPONSIBILITY FOR ANY BUSINESS INTERRUPTION OR LOSS OF DATA ARISING FROM THE AUTOMATIC TERMINATION OF THE LICENSE RIGHTS GRANTED HEREIN AND ANY ASSOCIATED CESSATION OF THE PRE-RELEASE SOFTWARE FUNCTIONS OR ANY UNANTICIPATED OR UNSCHEDULED DOWNTIME FOR ANY REASON OR ANY DELETION, CORRUPTION OR DAMAGE OF DATA ON OR THROUGH THE PRE-RELEASE SOFTWARE. SPLUNK'S TOTAL CUMULATIVE LIABILITY TO YOU, FROM ALL CAUSES OF ACTION AND ALL THEORIES OF LIABILITY, WILL BE LIMITED TO AND WILL NOT EXCEED ONE HUNDRED DOLLARS ($100.00). BECAUSE SOME STATES OR JURISDICTIONS DO NOT ALLOW LIMITATION OR EXCLUSION OF CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. + +12. PRIVACY. You acknowledge that Splunk and its providers may obtain information and data from you in connection with your registration, installation, and use of the Pre-Release Software, including, without limitation, personal information. Splunk may also collect and process technical and related environmental or performance information about your use of the Pre-Release Software (which may include, without limitation, data ingest volume, search concurrency, number of unique user log-ins, Internet protocol addresses, operating system, configuration information, application software, session duration, page view, operational use, and other such information) and use this information to support and troubleshoot issues, provide updates, analyze trends and improve Splunk’s products or services. You hereby consent to Splunk and its providers maintaining, using, storing and disclosing such information and data (including, without limitation, personal information, if any, in conformity with Splunk Privacy Policy, which may be updated from time to time). You further consent to receiving electronic communications and notifications from Splunk in connection with your use of the Pre-Release Software. + +13. EXPORT. You will comply fully with all relevant export laws and regulations of the United States and any other country (“Export Laws”) where you use any of the Pre-Release Software. You represent and warrant that you are not (a) located in, or a resident or a national of, a restricted country; or (b) on any of the relevant U.S. Government Lists of prohibited persons, including but not limited to the Treasury Department’s List of Specially Designated Nationals and the Commerce Department’s List of Denied Persons or Entity List. You further represent and warrant that you shall not export, re-export, ship, transfer the Pre-Release Software to any restricted countries or restricted end users or use the Pre-Release Software in any restricted countries or for any purposes prohibited by the Export Laws, including, but not limited to, nuclear, chemical, missile or biological weapons related end uses. You understand that the requirements and restrictions of the Export Laws may vary depending on the specific Pre-Release Software and may change over time, and that, to determine the precise controls applicable to the Pre-Release Software, it is necessary to refer to the U.S. Export Administration Regulations and the U.S. Department of Treasury, Office of Foreign Assets Control Regulations. + +14. GOVERNMENT END USER RIGHTS. You acknowledge that all Pre-Release Software and Confidential Information were developed entirely at private expense and that no part of the Pre-Release Software or Confidential Information was first produced in the performance of a Government contract. You agree that the Pre-Release Software and any derivatives thereof are "commercial items" as defined in 48 C.F.R. § 2.101, and if you are a U.S. Government agency or instrumentality or if you are providing all or any part of the Pre-Release Software or any derivatives thereof to the U.S. Government, such use, duplication, reproduction, release, modification, disclosure or transfer of this commercial product and data, is restricted in accordance with 48 C.F.R. § 12.211, 48 C.F.R. § 12.212, 48 C.F.R. § 227.7102-2, and 48 C.F.R. § 227.7202, as applicable. Consistent with 48 C.F.R. § 12.211, 48 C.F.R. § 12.212, 48 C.F.R. § 227.7102-1 through 48 C.F.R. § 227.7102-3, and 48 C.F.R. §§ 227.7202-1 through 227.7202-4, as applicable, the Pre-Release Software is licensed to U.S. Government end users (i) only as Commercial Items and (ii) with only those rights as are granted to all other users pursuant to this Agreement and any related agreement(s), as applicable. Accordingly, you will have no rights in the Pre-Release Software except as expressly agreed to in writing by you and Splunk. + +15. CHOICE OF LAW AND DISPUTES. For other than the U.S. Government as a party, this Agreement shall be governed by and construed in accordance with the laws of the State of California, as if performed wholly within the state and without giving effect to the principles of conflict of law rules of any jurisdiction or the United Nations Convention on Contracts for the International Sale of Goods, the application of which is expressly excluded. Any legal action or proceeding arising under this Agreement will be brought exclusively in the federal or state courts located in San Francisco, California and the parties hereby consent to personal jurisdiction and venue therein. + +16. GENERAL. All notices required or permitted under this Agreement hereto will be in writing and delivered in person, by confirmed facsimile transmission, by overnight delivery service, or by registered or certified mail, postage prepaid with return receipt requested, and in each instance will be deemed given upon receipt. You may not assign, delegate or transfer this Agreement, in whole or in part, by agreement, operation of law or otherwise. You acknowledge that Splunk may assign, subcontract or delegate any of its rights or obligations under this Agreement. Any attempt to assign this Agreement other than as permitted herein shall be null and void. Subject to the foregoing, this Agreement will bind and inure to the benefit of the parties’ permitted successors and assigns. This Agreement along with any additional terms incorporated herein by reference constitute the complete and exclusive understanding and agreement between the parties relating only to the subject matter of the Pre-Release Software and Confidential Information and shall supersede any and all prior or contemporaneous agreements, communications and understandings, written or oral, relating to such subject matter. This Agreement is limited to the use of Pre-Release Software and Confidential Information and as such, this Agreement is separate from and shall have no effect on any other agreement you may have with Splunk. Any waiver, modification or amendment of any provision of this Agreement will be effective only if in writing and signed by duly authorized representatives of both parties. All rights and remedies, whether conferred hereunder or by any other instrument or law, will be cumulative and may be exercised singularly or concurrently. The failure by either party to enforce any provisions of this Agreement will not constitute a waiver of any other right hereunder or of any subsequent enforcement of that or any other provisions. The terms and conditions stated herein are declared to be severable. If a court of competent jurisdiction holds any provision of this Agreement invalid or unenforceable, the remaining provisions of the Agreement will remain in full force and effect, and the provision affected will be construed so as to be enforceable to the maximum extent permissible by law. + +Updated February 16, 2017 \ No newline at end of file diff --git a/splunk_add_on_ucc_framework/templates/app.manifest.init-template b/splunk_add_on_ucc_framework/templates/app.manifest.init-template index 16a839b2c..edbe03360 100644 --- a/splunk_add_on_ucc_framework/templates/app.manifest.init-template +++ b/splunk_add_on_ucc_framework/templates/app.manifest.init-template @@ -9,7 +9,11 @@ }, "author": [ { + {% if include_author -%} + "name": "{{include_author}}", + {%- else -%} "name": "", + {%- endif %} "email": null, "company": null } @@ -25,8 +29,13 @@ }, "commonInformationModels": null, "license": { - "name": null, - "text": "LICENSE.txt", + {% if add_license -%} + "name": "{{add_license}}", + "text": "./LICENSES/{{add_license}}.txt", + {%- else -%} + "name": "LICENSE.txt", + "text": "./LICENSES/LICENSE.txt", + {%- endif %} "uri": null }, "privacyPolicy": { diff --git a/splunk_add_on_ucc_framework/utils.py b/splunk_add_on_ucc_framework/utils.py index 5872de3bf..56f995ad0 100644 --- a/splunk_add_on_ucc_framework/utils.py +++ b/splunk_add_on_ucc_framework/utils.py @@ -18,7 +18,8 @@ import shutil from os import listdir, makedirs, path, remove, sep from os.path import basename as bn -from os.path import dirname, exists, isdir, join +from os.path import dirname, exists, isdir, join, isfile +from splunk_add_on_ucc_framework.app_manifest import AppManifest from typing import Any, Dict import addonfactory_splunk_conf_parser_lib as conf_parser @@ -38,6 +39,26 @@ def get_j2_env() -> jinja2.Environment: ) +def get_license_path(file_name: str) -> str: + return join(dirname(__file__), "templates", "Licenses", f"{file_name}.txt") + + +def check_author_name(source: str, app_manifest: AppManifest) -> None: + check_path = join(source, "default", "app.conf") + if isfile(check_path): + app_conf = conf_parser.TABConfigParser() + app_conf.read(check_path) + app_conf_content = app_conf.item_dict() + if ( + app_manifest.get_authors()[0]["name"] + != app_conf_content["launcher"]["author"] + ): + logger.warning( + "Conflicting author names are identified between app.manifest and app.conf in the source directory. " + "Please specify the author name in app.manifest." + ) + + def recursive_overwrite(src: str, dest: str, ui_source_map: bool = False) -> None: """ Method to copy from src to dest recursively. diff --git a/tests/smoke/test_ucc_build.py b/tests/smoke/test_ucc_build.py index 22f8de63c..0620e5b33 100644 --- a/tests/smoke/test_ucc_build.py +++ b/tests/smoke/test_ucc_build.py @@ -1,3 +1,4 @@ +import sys import os import re import tempfile @@ -189,6 +190,7 @@ def test_ucc_generate_with_everything(caplog): ("appserver", "static", "alerticon.png"), ("bin", "splunk_ta_uccexample", "modalert_test_alert_helper.py"), ("appserver", "static", "js", "build", "entry_page.js.map"), + ("lib", "__pycache__"), ] for af in files_should_be_absent: actual_file_path = path.join(actual_folder, *af) @@ -467,6 +469,8 @@ def test_ucc_generate_with_everything_uccignore(caplog): Checks the deprecation warning of .uccignore present in a repo with its functionality still working. """ + # clean-up cached `additional_packaging` module when running all tests + sys.modules.pop("additional_packaging", "") with tempfile.TemporaryDirectory() as temp_dir: package_folder = path.join( path.dirname(path.realpath(__file__)), diff --git a/tests/smoke/test_ucc_init.py b/tests/smoke/test_ucc_init.py index 6f462e9bb..3eab0ac96 100644 --- a/tests/smoke/test_ucc_init.py +++ b/tests/smoke/test_ucc_init.py @@ -17,6 +17,8 @@ def test_ucc_init(): "1.0.0", "demo-addon-for-splunk", overwrite=True, + add_license="MIT License", + include_author="test_author", ) expected_folder = os.path.join( os.path.dirname(__file__), @@ -30,10 +32,10 @@ def test_ucc_init(): ("README.md",), ("globalConfig.json",), ("package", "README.txt"), - ("package", "LICENSE.txt"), ("package", "app.manifest"), ("package", "bin", "demo_input_helper.py"), ("package", "lib", "requirements.txt"), + ("package", "LICENSES", "MIT License.txt"), ] helpers.compare_file_content( files_to_be_equal, @@ -66,3 +68,14 @@ def test_ucc_init_if_same_output_then_sys_exit(): "demo_input", "1.0.0", ) + + +def test_ucc_init_empty_string_passed_for_author(): + with pytest.raises(SystemExit): + init.init( + "test_addon", + "Demo Add-on for Splunk", + "demo_input", + "1.0.0", + include_author="", + ) diff --git a/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSES/MIT License.txt b/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSES/MIT License.txt new file mode 100644 index 000000000..03e8bb5ab --- /dev/null +++ b/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSES/MIT License.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright [yyyy] [name of copyright owner] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/app.manifest b/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/app.manifest index 13470c6d0..4f4817f5b 100644 --- a/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/app.manifest +++ b/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/app.manifest @@ -9,7 +9,7 @@ }, "author": [ { - "name": "", + "name": "test_author", "email": null, "company": null } @@ -25,8 +25,8 @@ }, "commonInformationModels": null, "license": { - "name": null, - "text": "LICENSE.txt", + "name": "MIT License", + "text": "./LICENSES/MIT License.txt", "uri": null }, "privacyPolicy": { diff --git a/tests/testdata/expected_addons/expected_files_conflict_test/expected_log.json b/tests/testdata/expected_addons/expected_files_conflict_test/expected_log.json index 0aa40f75e..f6a38719c 100644 --- a/tests/testdata/expected_addons/expected_files_conflict_test/expected_log.json +++ b/tests/testdata/expected_addons/expected_files_conflict_test/expected_log.json @@ -2,7 +2,7 @@ "Detailed information about created/copied/modified/conflict files": "INFO", "Read more about it here: https://splunk.github.io/addonfactory-ucc-generator/quickstart/#verbose-mode": "INFO", "\u001b[33mapp.manifest modified\u001b[0m": "INFO", - "\u001b[32mLICENSE.txt copied\u001b[0m": "INFO", + "\u001b[32mLICENSES/LICENSE.txt copied\u001b[0m": "INFO", "\u001b[32mREADME.txt copied\u001b[0m": "INFO", "VERSION created\u001b[0m": "INFO", "\u001b[31mbin/import_declare_test.py conflict\u001b[0m": "INFO", diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/.appinspect_api.expect.yaml b/tests/testdata/expected_addons/expected_output_global_config_everything/.appinspect_api.expect.yaml index d57b094a2..e69de29bb 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/.appinspect_api.expect.yaml +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/.appinspect_api.expect.yaml @@ -1,2 +0,0 @@ -check_for_compiled_python: - comment: 'Expected failure as compiled python file was detected in your build.' \ No newline at end of file diff --git a/tests/testdata/test_addons/package_global_config_everything/LICENSES/Apache-2.0.txt b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache License 2.0.txt similarity index 100% rename from tests/testdata/test_addons/package_global_config_everything/LICENSES/Apache-2.0.txt rename to tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/LICENSES/Apache License 2.0.txt diff --git a/tests/testdata/test_addons/package_files_conflict_test/package/LICENSE.txt b/tests/testdata/test_addons/package_files_conflict_test/package/LICENSE.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSE.txt b/tests/testdata/test_addons/package_files_conflict_test/package/LICENSES/LICENSE.txt similarity index 100% rename from tests/testdata/expected_addons/expected_addon_after_init/demo_addon_for_splunk/package/LICENSE.txt rename to tests/testdata/test_addons/package_files_conflict_test/package/LICENSES/LICENSE.txt diff --git a/tests/testdata/test_addons/package_global_config_everything/additional_packaging.py b/tests/testdata/test_addons/package_global_config_everything/additional_packaging.py new file mode 100644 index 000000000..208d9e5ef --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything/additional_packaging.py @@ -0,0 +1,36 @@ +from os.path import sep, exists, dirname, realpath, join +from os import remove, system, _exit, WEXITSTATUS + +def additional_packaging(ta_name=None): + """ + `build-ui.sh` builds custom component present in source code and ships them in the output directory + """ + if exists( + join(dirname(realpath(__file__)), "build-ui.sh") + ): + system("chmod +x ./build-ui.sh") + return_code = system("./build-ui.sh") + if return_code != 0: + _exit(WEXITSTATUS(return_code)) + +def cleanup_output_files(output_path: str, ta_name: str) -> None: + """ + prepare a list for the files to be deleted after the source code has been copied to output directory + :param output_path: The path provided in `--output` argument in ucc-gen command or the default output path. + :param ta_name: The add-on name which is passed as a part of `--addon-name` argument during `ucc-gen init` + or present in app.manifest file of add-on. + """ + files_to_delete = [] + files_to_delete.append(sep.join([output_path, ta_name, "default", "redundant.conf"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "template_modinput_layout.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "example_one_input_one.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "template_rest_handler_script.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "file_does_not_exist.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "default", "nav", "views", "file_copied_from_source_code.xml"])) + + for delete_file in files_to_delete: + try: + remove(delete_file) + except (FileNotFoundError): + # simply pass if the file doesn't exist + pass \ No newline at end of file diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index 23510a926..ad1e2568a 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1980,7 +1980,7 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.52.0+70c7e9d6b", + "version": "5.53.2+ed749a5ec", "displayName": "Splunk UCC test Add-on", "schemaVersion": "0.0.9", "_uccVersion": "5.53.1", diff --git a/tests/testdata/test_addons/package_global_config_everything/package/LICENSES/Apache License 2.0.txt b/tests/testdata/test_addons/package_global_config_everything/package/LICENSES/Apache License 2.0.txt new file mode 100644 index 000000000..4ed90b952 --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything/package/LICENSES/Apache License 2.0.txt @@ -0,0 +1,208 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, +AND DISTRIBUTION + + 1. Definitions. + + + +"License" shall mean the terms and conditions for use, reproduction, and distribution +as defined by Sections 1 through 9 of this document. + + + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + + + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct +or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more +of the outstanding shares, or (iii) beneficial ownership of such entity. + + + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions +granted by this License. + + + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + + + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + + + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that +is included in or attached to the work (an example is provided in the Appendix +below). + + + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative +Works shall not include works that remain separable from, or merely link (or +bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in +the Work by the copyright owner or by an individual or Legal Entity authorized +to submit on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + + + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently incorporated +within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and distribute +the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, +each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and otherwise +transfer the Work, where such license applies only to those patent claims +licensable by such Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work to which such +Contribution(s) was submitted. If You institute patent litigation against +any entity (including a cross-claim or counterclaim in a lawsuit) alleging +that the Work or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses granted to You +under this License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and +in Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source +form of the Work, excluding those notices that do not pertain to any part +of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable copy +of the attribution notices contained within such NOTICE file, excluding those +notices that do not pertain to any part of the Derivative Works, in at least +one of the following places: within a NOTICE text file distributed as part +of the Derivative Works; within the Source form or documentation, if provided +along with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text from the +Work, provided that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, +or distribution of Your modifications, or for any such Derivative Works as +a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without +any additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to +in writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR +A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness +of using or redistributing the Work and assume any risks associated with Your +exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether +in tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to +in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the possibility +of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work +or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such obligations, +You may act only on Your own behalf and on Your sole responsibility, not on +behalf of any other Contributor, and only if You agree to indemnify, defend, +and hold each Contributor harmless for any liability incurred by, or claims +asserted against, such Contributor by reason of your accepting any such warranty +or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own identifying +information. (Don't include the brackets!) The text should be enclosed in +the appropriate comment syntax for the file format. We also recommend that +a file or class name and description of purpose be included on the same "printed +page" as the copyright notice for easier identification within third-party +archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. diff --git a/tests/testdata/test_addons/package_global_config_everything/package/app.manifest b/tests/testdata/test_addons/package_global_config_everything/package/app.manifest index e42e5a046..c947b9b82 100644 --- a/tests/testdata/test_addons/package_global_config_everything/package/app.manifest +++ b/tests/testdata/test_addons/package_global_config_everything/package/app.manifest @@ -24,7 +24,7 @@ "commonInformationModels": null, "license": { "name": null, - "text": "LICENSES/Apache-2.0.txt", + "text": "LICENSES/Apache License 2.0.txt", "uri": null }, "privacyPolicy": { diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/LICENSES/Apache-2.0.txt b/tests/testdata/test_addons/package_global_config_everything_uccignore/LICENSES/Apache-2.0.txt deleted file mode 100644 index 33195b0f8..000000000 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/LICENSES/Apache-2.0.txt +++ /dev/null @@ -1 +0,0 @@ -dummy apache license \ No newline at end of file diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py b/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py index 8af85b880..5b62ad240 100644 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py @@ -1,6 +1,6 @@ -from os.path import sep, exists, dirname, realpath, join -from os import remove, system, _exit, WEXITSTATUS +from os.path import sep +from os import remove def cleanup_output_files(output_path: str, ta_name: str) -> None: """ diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/package/LICENSES/Apache License 2.0.txt b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/LICENSES/Apache License 2.0.txt new file mode 100644 index 000000000..775f420fa --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/LICENSES/Apache License 2.0.txt @@ -0,0 +1 @@ +test Apache license diff --git a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json index 8002ac8db..b6393c616 100644 --- a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json @@ -387,63 +387,226 @@ "name": "example_input_one", "description": "This is a description for Input One", "title": "Example Input", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [] + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ] }, "warning": { - "create": { - "message": "Warning text for create mode" - }, - "edit": { - "message": "Warning text for edit mode" - }, - "clone": { - "message": "Warning text for clone mode" - }, - "config": { - "message": "Warning text for config mode" - } + "create": { + "message": "Warning text for create mode" + }, + "edit": { + "message": "Warning text for edit mode" + }, + "clone": { + "message": "Warning text for clone mode" + }, + "config": { + "message": "Warning text for config mode" + } } }, { "name": "example_input_two", "description": "This is a description for Input Two", "title": "Example Input Two", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" } - } + }, + "useInputToggleConfirmation": true }, { "name": "example_input_three", "description": "Input hidden for cloud", "title": "Example Input Three Hidden Cloud", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" @@ -455,15 +618,69 @@ "name": "example_input_four", "description": "Input hidden for enterprise", "title": "Example Input Four Hidden Enterprise", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" @@ -477,9 +694,9 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.50.1+099cf36c", + "version": "5.52.0+2e44cba94", "displayName": "Splunk UCC test Add-on", - "schemaVersion": "0.0.8", - "_uccVersion": "5.50.1" + "schemaVersion": "0.0.9", + "_uccVersion": "5.52.0" } } diff --git a/tests/ui/test_configuration_page_account_tab.py b/tests/ui/test_configuration_page_account_tab.py index 6617ff7d4..204f3e607 100644 --- a/tests/ui/test_configuration_page_account_tab.py +++ b/tests/ui/test_configuration_page_account_tab.py @@ -1033,8 +1033,11 @@ def test_account_helplink(self, ucc_smartx_selenium_helper, ucc_smartx_rest_help account = AccountPage(ucc_smartx_selenium_helper, ucc_smartx_rest_helper) go_to_link = "https://docs.splunk.com/Documentation" account.entity.open() - with account.entity.help_link.open_link(): - self.assert_util(account.entity.help_link.get_current_url, go_to_link) + + assert ( + account.entity.help_link.internal_container.get_attribute("href") + == go_to_link + ) @pytest.mark.execute_enterprise_cloud_true @pytest.mark.forwarder diff --git a/tests/ui/test_input_page.py b/tests/ui/test_input_page.py index 176da970d..9876c7e62 100644 --- a/tests/ui/test_input_page.py +++ b/tests/ui/test_input_page.py @@ -1037,8 +1037,11 @@ def test_example_input_one_help_link( go_to_link = "https://docs.splunk.com/Documentation" input_page.create_new_input.select("Example Input One") input_page.entity1.example_account.wait_for_values() - with input_page.entity1.help_link.open_link(): - self.assert_util(input_page.entity1.help_link.get_current_url, go_to_link) + + assert ( + input_page.entity1.help_link.internal_container.get_attribute("href") + == go_to_link + ) @pytest.mark.execute_enterprise_cloud_true @pytest.mark.forwarder diff --git a/tests/unit/commands/test_init.py b/tests/unit/commands/test_init.py index 74bcf3166..37d0f10b3 100644 --- a/tests/unit/commands/test_init.py +++ b/tests/unit/commands/test_init.py @@ -93,6 +93,8 @@ def test__is_valid_input_name(input_name, expected): "0.0.1", "addon_name", False, + None, + None, ), ), ( @@ -110,6 +112,8 @@ def test__is_valid_input_name(input_name, expected): "0.0.1", "addon_name", False, + None, + None, ), ), ( @@ -119,6 +123,7 @@ def test__is_valid_input_name(input_name, expected): "addon_display_name": "Addon For Demo", "addon_input_name": "input_name", "addon_version": "0.0.1", + "add_license": "Apache License 2.0", }, ( "addon_name", @@ -127,6 +132,52 @@ def test__is_valid_input_name(input_name, expected): "0.0.1", "addon_rest_root", False, + "Apache License 2.0", + None, + ), + ), + ( + { + "addon_name": "addon_name", + "addon_rest_root": "addon_rest_root", + "addon_display_name": "Addon For Demo", + "addon_input_name": "input_name", + "addon_version": "0.0.1", + "overwrite": True, + "add_license": "Apache License 2.0", + "include_author": "test_author", + }, + ( + "addon_name", + "Addon For Demo", + "input_name", + "0.0.1", + "addon_rest_root", + True, + "Apache License 2.0", + "test_author", + ), + ), + ( + { + "addon_name": "addon_name", + "addon_rest_root": "addon_rest_root", + "addon_display_name": "Addon For Demo", + "addon_input_name": "input_name", + "addon_version": "0.0.1", + "overwrite": True, + "add_license": "Apache License 2.0", + "include_author": " test author ", + }, + ( + "addon_name", + "Addon For Demo", + "input_name", + "0.0.1", + "addon_rest_root", + True, + "Apache License 2.0", + "test author", ), ), ], @@ -174,6 +225,16 @@ def test_init(mock_generate_addon, init_kwargs, expected_args_to_generate_addon) "addon_version": "0.0.1", } ), + ( + { + "addon_name": "addon_name", + "addon_rest_root": "addon_rest_root", + "addon_display_name": "Addon For Demo", + "addon_input_name": "input_name", + "addon_version": "0.0.1", + "include_author": "", + } + ), ], ) def test_init_when_incorrect_parameters_then_sys_exit(init_kwargs): @@ -199,6 +260,24 @@ def test_init_when_folder_already_exists(mock_generate_addon, caplog): assert expected_error_message in caplog.text +@mock.patch("splunk_add_on_ucc_framework.commands.init._generate_addon") +def test_init_when_empty_string_passed_for_author(mock_generate_addon, caplog): + mock_generate_addon.side_effect = SystemExit + + with pytest.raises(SystemExit): + init.init( + "test_addon", + "Addon For Demo Already Exists", + "input_name", + "0.0.1", + include_author="", + ) + expected_error_message = ( + "The author name cannot be left empty, please provide some input. " + ) + assert expected_error_message in caplog.text + + def test_valid_regex(): file_path = f"{helpers.get_path_to_source_dir()}/schema/schema.json" with open(file_path) as file: diff --git a/tests/unit/test_global_config_validator.py b/tests/unit/test_global_config_validator.py index fd365a374..43dc3b9f2 100644 --- a/tests/unit/test_global_config_validator.py +++ b/tests/unit/test_global_config_validator.py @@ -579,3 +579,15 @@ def test_should_warn_on_empty_validators(schema_json): field["validators"] = [number_validator] assert not should_warn_on_empty_validators(oauth_entity) + + +def test_config_validation_status_toggle_confirmation(): + global_config_path = helpers.get_testdata_file_path( + "valid_config_with_input_status_confirmation.json" + ) + global_config = global_config_lib.GlobalConfig(global_config_path) + + validator = GlobalConfigValidator(helpers.get_path_to_source_dir(), global_config) + + with does_not_raise(): + validator.validate() diff --git a/tests/unit/test_install_python_libraries.py b/tests/unit/test_install_python_libraries.py index 8e2d4972a..d6740fbf0 100644 --- a/tests/unit/test_install_python_libraries.py +++ b/tests/unit/test_install_python_libraries.py @@ -1,12 +1,17 @@ import os import stat +from collections import namedtuple from typing import List from unittest import mock import pytest + import tests.unit.helpers as helpers from splunk_add_on_ucc_framework.global_config import OSDependentLibraryConfig +from splunk_add_on_ucc_framework import ( + install_python_libraries as install_python_libraries_module, +) from splunk_add_on_ucc_framework.install_python_libraries import ( CouldNotInstallRequirements, SplunktaucclibNotFound, @@ -24,8 +29,9 @@ class MockSubprocessResult: - def __init__(self, returncode): + def __init__(self, returncode, stdout=b""): self.returncode = returncode + self.stdout = stdout @mock.patch("subprocess.run", autospec=True) @@ -277,8 +283,23 @@ def test_install_libraries_valid_os_libraries( "valid_config_with_os_libraries.json" ) global_config = gc.GlobalConfig(global_config_path) - - mock_subprocess_run.return_value = MockSubprocessResult(0) + mock_subprocess_run.side_effect = [ + MockSubprocessResult(0), # mock subprocess.run from _pip_install + MockSubprocessResult(0), # mock subprocess.run from _pip_install + MockSubprocessResult( + 0, b"Version: 41.0.5" + ), # mock subprocess.run from _pip_is_lib_installed + MockSubprocessResult(0), # mock subprocess.run from _pip_install + MockSubprocessResult( + 0, b"Version: 41.0.5" + ), # mock subprocess.run from _pip_is_lib_installed + MockSubprocessResult(0), # mock subprocess.run from _pip_install + MockSubprocessResult( + 0, b"Version: 1.5.1" + ), # mock subprocess.run from _pip_is_lib_installed + MockSubprocessResult(0), # mock subprocess.run from _pip_install + MockSubprocessResult(0), # mock subprocess.run from _pip_install + ] tmp_ucc_lib_target = tmp_path / "ucc-lib-target" tmp_lib_path = tmp_path / "lib" tmp_lib_path.mkdir() @@ -365,15 +386,14 @@ def test_install_libraries_version_mismatch( tmp_lib_reqs_file = tmp_lib_path / "requirements.txt" tmp_lib_reqs_file.write_text("splunktaucclib\n") - version_mismatch_shell_cmd = ( - 'python3 -m pip show --version cryptography | grep "Version: 41.0.5"' - ) + version_mismatch_shell_cmd = "python3 -m pip show --version cryptography" mock_subprocess_run.side_effect = ( - lambda command, shell=True, env=None, capture_output=True: MockSubprocessResult( - 1 + lambda command, shell=True, env=None, capture_output=True: ( + MockSubprocessResult(1) + if command == version_mismatch_shell_cmd + and ucc_lib_target == env["PYTHONPATH"] + else MockSubprocessResult(0, b"Version: 40.0.0") ) - if command == version_mismatch_shell_cmd and ucc_lib_target == env["PYTHONPATH"] - else MockSubprocessResult(0) ) with pytest.raises(CouldNotInstallRequirements): @@ -509,6 +529,19 @@ def test_is_pip_lib_installed_wrong_arguments(): _pip_is_lib_installed("i", "t", "l", allow_higher_version=True) +def test_is_pip_lib_installed_do_not_write_bytecode(monkeypatch): + Result = namedtuple("Result", ["returncode", "stdout", "stderr"]) + + def run(command, env): + assert command == "python3 -m pip show --version libname" + assert env["PYTHONPATH"] == "target" + assert env["PYTHONDONTWRITEBYTECODE"] == "1" + return Result(0, b"Version: 1.0.0", b"") + + monkeypatch.setattr(install_python_libraries_module, "_subprocess_run", run) + assert _pip_is_lib_installed("python3", "target", "libname") + + @mock.patch("subprocess.run", autospec=True) def test_install_libraries_pip_custom_flag(mock_subprocess_run): mock_subprocess_run.return_value = MockSubprocessResult(0) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 6b8d187eb..9951a6162 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -348,6 +348,8 @@ def test_build_command(mock_ucc_gen_generate, args, expected_parameters): "addon_version": "0.0.1", "addon_rest_root": None, "overwrite": False, + "add_license": None, + "include_author": None, }, ), ( @@ -361,6 +363,8 @@ def test_build_command(mock_ucc_gen_generate, args, expected_parameters): "Splunk Add-on for Demo", "--addon-input-name", "demo_input", + "--add-license", + "MIT License", ], { "addon_name": "splunk_add_on_for_demo", @@ -369,6 +373,36 @@ def test_build_command(mock_ucc_gen_generate, args, expected_parameters): "addon_version": "0.0.1", "addon_rest_root": "splunk_add_on_for_demo", "overwrite": False, + "add_license": "MIT License", + "include_author": None, + }, + ), + ( + [ + "init", + "--addon-name", + "splunk_add_on_for_demo", + "--addon-rest-root", + "splunk_add_on_for_demo", + "--addon-display-name", + "Splunk Add-on for Demo", + "--addon-input-name", + "demo_input", + "--overwrite", + "--add-license", + "MIT License", + "--include-author", + "test_author", + ], + { + "addon_name": "splunk_add_on_for_demo", + "addon_display_name": "Splunk Add-on for Demo", + "addon_input_name": "demo_input", + "addon_version": "0.0.1", + "addon_rest_root": "splunk_add_on_for_demo", + "overwrite": True, + "add_license": "MIT License", + "include_author": "test_author", }, ), ], @@ -380,6 +414,25 @@ def test_init_command(mock_init_command, args, expected_parameters): mock_init_command.assert_called_with(**expected_parameters) +@pytest.mark.parametrize( + "args", + [ + ( + { + "addon_name": "splunk_add_on_for_demo", + "addon_display_name": "Addon For Demo", + "addon_input_name": "input_name", + "addon_version": "0.0.1", + "add_license": "Apache License", + } + ), + ], +) +def test_init_command_incorrect_license(args): + with pytest.raises(SystemExit): + main.main(args) + + @pytest.mark.parametrize( "args,expected_parameters", [ diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index a10bda04a..fa534f50b 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,6 +3,8 @@ import dunamai import pytest +from unittest.mock import patch, MagicMock +from os.path import join from splunk_add_on_ucc_framework import exceptions, utils @@ -35,6 +37,47 @@ def test_get_j2_env(): assert sorted(expected_list_of_templates) == sorted(list_of_templates) +@patch("splunk_add_on_ucc_framework.utils.__file__", "/mocked/path/utils") +def test_get_license_path(): + file_name = "example_license" + expected_path = "/mocked/path/templates/Licenses/example_license.txt" + actual_path = utils.get_license_path(file_name) + assert actual_path == expected_path + + +@patch("splunk_add_on_ucc_framework.utils.isfile") +@patch("splunk_add_on_ucc_framework.utils.conf_parser.TABConfigParser") +@patch("splunk_add_on_ucc_framework.utils.logger") +def test_check_author_names_conflict(mock_logger, mock_tab_config_parser, mock_isfile): + source = "/path/to/source" + app_manifest = MagicMock() + app_manifest.get_authors.return_value = [{"name": "Author in Manifest"}] + + mock_isfile.return_value = True + app_conf_mock = MagicMock() + app_conf_mock.item_dict.return_value = {"launcher": {"author": "Author in Conf"}} + mock_tab_config_parser.return_value = app_conf_mock + utils.check_author_name(source, app_manifest) + + check_path = join(source, "default", "app.conf") + mock_isfile.assert_called_once_with(check_path) + mock_logger.warning.assert_called_once_with( + "Conflicting author names are identified between app.manifest and app.conf in the source directory. " + "Please specify the author name in app.manifest." + ) + + +@patch("splunk_add_on_ucc_framework.utils.isfile") +def test_check_author_names_no_conflict(mock_isfile): + source = "/path/to/source" + app_manifest = MagicMock() + app_manifest.get_authors.return_value = [{"name": "Author in Manifest"}] + + mock_isfile.return_value = False + utils.check_author_name(source, app_manifest) + mock_isfile.assert_called_once_with(join(source, "default", "app.conf")) + + @mock.patch("splunk_add_on_ucc_framework.utils.dunamai.Version", autospec=True) def test_get_version_from_git_when_runtime_error_from_dunamai(mock_version_class): mock_version_class.from_git.side_effect = RuntimeError @@ -121,3 +164,17 @@ def test_dump_yaml_config(tmp_path): content = f.read() assert expected_content == content + + +@pytest.mark.parametrize( + "test_path,expected_path", + [ + ("/home/john/Test/test.txt", "home/john/Test/test.txt"), + ("\\home\\john\\Test\\test.txt", "home/john/Test/test.txt"), + ("\\\\home\\\\john\\\\Test\\\\test.txt", "home/john/Test/test.txt"), + ], +) +def test_get_os_path(test_path, expected_path): + stripped_path = utils.get_os_path(test_path) + + assert stripped_path == expected_path diff --git a/tests/unit/testdata/valid_config_with_input_status_confirmation.json b/tests/unit/testdata/valid_config_with_input_status_confirmation.json new file mode 100644 index 000000000..d8189ca5f --- /dev/null +++ b/tests/unit/testdata/valid_config_with_input_status_confirmation.json @@ -0,0 +1,90 @@ +{ + "pages": { + "configuration": { + "tabs": [ + { + "name": "account", + "table": { + "actions": ["edit", "delete", "clone"], + "header": [ + { + "label": "Name", + "field": "name" + } + ] + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "string", + "errorMsg": "Length of ID should be between 1 and 50", + "minLength": 1, + "maxLength": 50 + }, + { + "type": "regex", + "errorMsg": "Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "name", + "help": "Enter a unique name for this account.", + "required": true + } + ], + "title": "Account" + } + ], + "title": "Configuration", + "description": "Set up your add-on" + }, + "inputs": { + "title": "Status toggle confirmation test", + "services": [ + { + "name": "example_input_one", + "title": "Input one status toggle confirmation test", + "entity": [ + { + "type": "text", + "label": "Name", + "field": "name" + } + ] + }, + { + "name": "example_input_two", + "title": "Input two status toggle confirmation test", + "entity": [ + { + "type": "text", + "label": "Name", + "field": "name" + } + ] + } + ], + "table": { + "actions": ["edit", "delete", "clone"], + "header": [ + { + "label": "Name", + "field": "name" + } + ] + }, + "useInputToggleConfirmation": true + } + }, + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "splunk_ta_uccexample", + "version": "5.39.0Ra9e840a6", + "displayName": "Splunk UCC test Add-on", + "schemaVersion": "0.0.3", + "_uccVersion": "5.39.0" + } +} diff --git a/ui/jest.polyfills.ts b/ui/jest.polyfills.ts index 94ad52b31..597b8c0eb 100644 --- a/ui/jest.polyfills.ts +++ b/ui/jest.polyfills.ts @@ -9,15 +9,19 @@ * you don't want to deal with this. */ +const { performance } = require('node:perf_hooks'); const { TextDecoder, TextEncoder } = require('node:util'); const { TransformStream } = require('node:stream/web'); const { BroadcastChannel } = require('node:worker_threads'); +const { clearImmediate } = require('node:timers'); Object.defineProperties(globalThis, { TextDecoder: { value: TextDecoder }, TextEncoder: { value: TextEncoder }, TransformStream: { value: TransformStream }, BroadcastChannel: { value: BroadcastChannel }, + clearImmediate: { value: clearImmediate }, + performance: { value: performance }, }); const { Blob } = require('node:buffer'); diff --git a/ui/src/components/ControlWrapper/ControlWrapper.tsx b/ui/src/components/ControlWrapper/ControlWrapper.tsx index 6719b6625..6014b3b8f 100644 --- a/ui/src/components/ControlWrapper/ControlWrapper.tsx +++ b/ui/src/components/ControlWrapper/ControlWrapper.tsx @@ -26,7 +26,7 @@ const ControlGroupWrapper = styled(ControlGroup).attrs((props: { dataName: strin } `; -interface ControlWrapperProps { +export interface ControlWrapperProps { mode: Mode; utilityFuncts: UtilControlWrapper; value: AcceptableFormValueOrNullish; @@ -128,12 +128,17 @@ class ControlWrapper extends React.PureComponent { ); - const isFieldRequired = // modifiedEntitiesData takes precedence over entity - this.props?.modifiedEntitiesData?.required || this.props.entity?.required === undefined - ? 'oauth_field' in (this.props.entity || {}) // if required is undefined use true for oauth fields and false for others - : this.props.entity?.required; // if required present use required - const label = this.props?.modifiedEntitiesData?.label || this?.props?.entity?.label || ''; + const isRequiredModified = + typeof this.props?.modifiedEntitiesData?.required === 'boolean' + ? this.props?.modifiedEntitiesData?.required + : this.props.entity?.required; + + const isFieldRequired = + isRequiredModified === undefined // // if oauth_field exists field required by default + ? 'oauth_field' in (this.props.entity || {}) // if oauth_field does not exists not required by default + : isRequiredModified; + const label = this.props?.modifiedEntitiesData?.label || this?.props?.entity?.label || ''; return ( this.props.display && ( ) => { + render( + {}, + setErrorFieldMsg: () => {}, + clearAllErrorMsg: () => {}, + setErrorMsg: () => {}, + }, + handleChange: () => {}, + addCustomValidator: () => {}, + }} + value="" + display + error={false} + disabled={false} + serviceName="testServiceName" + dependencyValues={undefined} + entity={{ + field: 'url', + label: 'URL', + type: 'text', + help: 'Enter the URL, for example', + required: true, + validators: [ + { + errorMsg: + "Invalid URL provided. URL should start with 'https' as only secure URLs are supported. Provide URL in this format", + type: 'regex', + pattern: '^(https://)[^/]+/?$', + }, + ], + encrypted: false, + }} + {...props} + /> + ); +}; + +it('check if required star displayed correctly', () => { + renderControlWrapper({}); + const requiredStar = screen.queryByText('*'); + expect(requiredStar).toBeInTheDocument(); +}); + +it('check if required star not displayed', () => { + renderControlWrapper({ + entity: { + field: 'url', + label: 'URL', + type: 'text', + required: false, + }, + }); + const requiredStar = screen.queryByText('*'); + expect(requiredStar).not.toBeInTheDocument(); +}); + +it('check if required star displayed correctly from modifiedEntitiesData', () => { + renderControlWrapper({ + entity: { + field: 'url', + label: 'URL', + type: 'text', + required: false, + }, + modifiedEntitiesData: { required: true }, + }); + const requiredStar = screen.queryByText('*'); + expect(requiredStar).toBeInTheDocument(); +}); + +it('check if required star not displayed due to modifiedEntitiesData', () => { + renderControlWrapper({ + entity: { + field: 'url', + label: 'URL', + type: 'text', + required: true, + }, + modifiedEntitiesData: { required: false }, + }); + + const requiredStar = screen.queryByText('*'); + expect(requiredStar).not.toBeInTheDocument(); +}); + +it('check if label and help updated due to modifiedEntitiesData', () => { + const modifications = { required: false, label: 'Modified URL', help: 'Modified help' }; + renderControlWrapper({ + entity: { + field: 'url', + label: 'URL', + help: 'Enter the URL, for example', + type: 'text', + required: true, + }, + modifiedEntitiesData: modifications, + }); + + const label = screen.getByTestId('label'); // label replaced + expect(label).toHaveTextContent(modifications.label); + + const help = screen.getByTestId('help'); // help replaced + expect(help).toHaveTextContent(modifications.help); +}); + +it('check if help added due to modifiedEntitiesData', () => { + const modifications = { help: 'Modified help' }; + + renderControlWrapper({ + entity: { + field: 'url', + label: 'URL', + type: 'text', + required: true, + }, + modifiedEntitiesData: modifications, + }); + + const help = screen.getByTestId('help'); + expect(help).toHaveTextContent(modifications.help); +}); diff --git a/ui/src/components/DeleteModal/DeleteModal.test.tsx b/ui/src/components/DeleteModal/DeleteModal.test.tsx index 37de666b3..dbd8dbc4d 100644 --- a/ui/src/components/DeleteModal/DeleteModal.test.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.test.tsx @@ -5,7 +5,6 @@ import { http, HttpResponse } from 'msw'; import DeleteModal from './DeleteModal'; import { server } from '../../mocks/server'; -jest.mock('immutability-helper'); jest.mock('../../util/util'); const handleClose = jest.fn(); diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx index 317ebe89a..58e944abb 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx @@ -78,7 +78,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { return; } - let current = true; + let mounted = true; const abortController = new AbortController(); const url = referenceName @@ -102,7 +102,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { setLoading(true); getRequest<{ entry: FilterResponseParams }>(apiCallOptions) .then((data) => { - if (current) { + if (mounted) { setOptions( generateOptions( filterResponse( @@ -117,15 +117,15 @@ function MultiInputComponent(props: MultiInputComponentProps) { } }) .finally(() => { - if (current) { + if (mounted) { setLoading(false); } }); } // eslint-disable-next-line consistent-return return () => { - abortController.abort('Operation canceled.'); - current = false; + mounted = false; + abortController.abort(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [dependencyValues]); diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx index 126a71892..7b32f749c 100755 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx @@ -128,7 +128,7 @@ function SingleInputComponent(props: SingleInputComponentProps) { return; } - let current = true; + let mounted = true; const abortController = new AbortController(); const backendCallOptions = { @@ -145,7 +145,7 @@ function SingleInputComponent(props: SingleInputComponentProps) { setLoading(true); getRequest<{ entry: FilterResponseParams }>(backendCallOptions) .then((data) => { - if (current) { + if (mounted) { setOptions( generateOptions( filterResponse(data.entry, labelField, valueField, allowList, denyList) @@ -155,16 +155,16 @@ function SingleInputComponent(props: SingleInputComponentProps) { } }) .catch(() => { - if (current) { + if (mounted) { setLoading(false); + setOptions([]); } - setOptions([]); }); // eslint-disable-next-line consistent-return return () => { - abortController.abort('Operation canceled.'); - current = false; + mounted = false; + abortController.abort(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [dependencyValues]); diff --git a/ui/src/components/table/CustomTable.tsx b/ui/src/components/table/CustomTable.tsx index 5ac928783..233461fbf 100644 --- a/ui/src/components/table/CustomTable.tsx +++ b/ui/src/components/table/CustomTable.tsx @@ -29,6 +29,7 @@ interface CustomTableProps { sortDir: SortDirection; sortKey?: string; tableConfig: ITableConfig; + useInputToggleConfirmation?: boolean; } interface IEntityModal { @@ -63,6 +64,7 @@ const CustomTable: React.FC = ({ sortDir, sortKey, tableConfig, + useInputToggleConfirmation, }) => { const unifiedConfigs: GlobalConfig = getUnifiedConfigs(); const [entityModal, setEntityModal] = useState({ open: false }); @@ -263,6 +265,7 @@ const CustomTable: React.FC = ({ rowActions={actions} headerMapping={headerMapping} readonly={isReadonlyRow(readonlyFieldId, row)} + useInputToggleConfirmation={useInputToggleConfirmation} {...{ handleEditActionClick, handleCloneActionClick, diff --git a/ui/src/components/table/CustomTableControl.jsx b/ui/src/components/table/CustomTableControl.jsx index 544dd32f2..6ffb0d5d2 100644 --- a/ui/src/components/table/CustomTableControl.jsx +++ b/ui/src/components/table/CustomTableControl.jsx @@ -21,75 +21,51 @@ class CustomTableControl extends Component { this.state = { loading: true, row: { ...props.row }, - checkMethodIsPresent: false, - methodNotPresentError: '', + checkMethodIsPresent: false, // Flag to track if methods are available in custom control + methodNotPresentError: '', // Stores error message if a method is missing + rowUpdatedByControl: false, // Flag to track if the row was updated by custom control }; - this.shouldRender = true; + this.shouldRender = true; // Flag to control rendering logic } - componentDidMount() { - const globalConfig = getUnifiedConfigs(); - this.loadCustomControl() - .then(async (Control) => { - if (typeof Control !== 'function') { - this.setState({ - loading: false, - methodNotPresentError: 'Loaded module is not a constructor function', - }); - return; - } - this.customControl = new Control( - globalConfig, - this.props.serviceName, - this.el, - this.state.row, - this.props.field - ); + // Lifecycle method that updates the component's state when props change + static getDerivedStateFromProps(nextProps, prevState) { + // Update row data only if the row prop has changed and it wasn't updated by control itself + if (!prevState.rowUpdatedByControl && nextProps.row !== prevState.row) { + return { + row: { ...nextProps.row }, + loading: false, // Set loading to false when new row data is received + }; + } + return null; + } - const result = await this.callCustomMethod('getDLRows'); - try { - // check if getDLRow is exist in the custom input row file - if (result && typeof result === 'object' && !Array.isArray(result)) { - this.setState({ - row: { ...result }, - checkMethodIsPresent: true, - loading: false, - }); - } else if (result !== null) { - // check if getDLRow return invalid object - this.setState({ - loading: false, - checkMethodIsPresent: true, - methodNotPresentError: 'getDLRows method did not return a valid object', - }); - } else { - // if getDLRow is not present then check render method is present or not - this.handleNoGetDLRows(); - } - } catch (error) { - onCustomControlError({ methodName: 'getDLRows', error }); - this.handleNoGetDLRows(); - } - }) - .catch(() => - this.setState({ - loading: false, - methodNotPresentError: 'Error loading custom control', - }) - ); + // Lifecycle method called after the component has been mounted (first render) + componentDidMount() { + this.initializeCustomControl(); } shouldComponentUpdate(nextProps, nextState) { - if (this.props.row !== nextProps.row) { + // Trigger re-render if row prop or state has changed + if (this.props.row !== nextProps.row || this.state.row !== nextState.row) { return true; } + // Check if loading state is false and shouldRender flag is true to trigger re-render if (!nextState.loading && this.shouldRender) { - this.shouldRender = false; + this.shouldRender = false; // Disable further re-renders return true; } return false; } + componentDidUpdate(prevProps) { + // If the row prop has changed, re-initialize the custom control + if (prevProps.row !== this.props.row) { + this.initializeCustomControl(); + this.setState({ rowUpdatedByControl: false }); + } + } + loadCustomControl = () => new Promise((resolve, reject) => { const { type, fileName } = this.props; @@ -142,6 +118,61 @@ class CustomTableControl extends Component { })); }; + // Function to initialize the custom control, loading the module and calling methods on it + async initializeCustomControl() { + const globalConfig = getUnifiedConfigs(); + this.loadCustomControl() + .then(async (Control) => { + if (typeof Control !== 'function') { + this.setState({ + loading: false, + methodNotPresentError: 'Loaded module is not a constructor function', + }); + return; + } + + this.customControl = new Control( + globalConfig, + this.props.serviceName, + this.el, + this.state.row, + this.props.field + ); + + // Call the "getDLRows" method on the custom control instance + const result = await this.callCustomMethod('getDLRows'); + try { + if (result && typeof result === 'object' && !Array.isArray(result)) { + // If getDLRows returns a valid object, update state with new row data + this.setState({ + row: { ...result }, + checkMethodIsPresent: true, + loading: false, + rowUpdatedByControl: true, + }); + } else if (result !== null) { + // If result is not valid, show an error + this.setState({ + loading: false, + checkMethodIsPresent: true, + methodNotPresentError: 'getDLRows method did not return a valid object', + }); + } else { + this.handleNoGetDLRows(); + } + } catch (error) { + onCustomControlError({ methodName: 'getDLRows', error }); + this.handleNoGetDLRows(); + } + }) + .catch(() => + this.setState({ + loading: false, + methodNotPresentError: 'Error loading custom control', + }) + ); + } + render() { const { row, loading, checkMethodIsPresent, methodNotPresentError } = this.state; const { moreInfo } = this.props; diff --git a/ui/src/components/table/CustomTableRow.tsx b/ui/src/components/table/CustomTableRow.tsx index bc4511485..daa6a1c5e 100644 --- a/ui/src/components/table/CustomTableRow.tsx +++ b/ui/src/components/table/CustomTableRow.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useCallback } from 'react'; +import React, { ReactElement, useCallback, useState } from 'react'; import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import Switch from '@splunk/react-ui/Switch'; @@ -15,6 +15,7 @@ import { _ } from '@splunk/ui-utils/i18n'; import CustomTableControl from './CustomTableControl'; import { ActionButtonComponent } from './CustomTableStyle'; import { getTableCellValue } from './table.utils'; +import AcceptModal from '../AcceptModal/AcceptModal'; import { RowDataFields } from '../../context/TableContext'; const TableCellWrapper = styled(Table.Cell)` @@ -40,6 +41,7 @@ interface CustomTableRowProps { handleEditActionClick: (row: RowDataFields) => void; handleCloneActionClick: (row: RowDataFields) => void; handleDeleteActionClick: (row: RowDataFields) => void; + useInputToggleConfirmation?: boolean; } interface CellHeader { @@ -57,8 +59,11 @@ function CustomTableRow(props: CustomTableRowProps) { handleEditActionClick, handleCloneActionClick, handleDeleteActionClick, + useInputToggleConfirmation, } = props; + const [displayAcceptToggling, setDisplayAcceptToggling] = useState(false); + const getCustomCell = (customRow: RowDataFields, header: CellHeader) => header.customCell?.src && header.customCell?.type && @@ -133,6 +138,17 @@ function CustomTableRow(props: CustomTableRowProps) { [handleEditActionClick, handleCloneActionClick, handleDeleteActionClick] ); + const handleAcceptModal = (accepted: boolean) => { + if (accepted) { + handleToggleActionClick(row); + } + setDisplayAcceptToggling(false); + }; + + const verifyToggleActionClick = () => { + setDisplayAcceptToggling(true); + }; + let statusContent: string | ReactElement = row.disabled ? 'Inactive' : 'Active'; // eslint-disable-next-line no-underscore-dangle if (row.__toggleShowSpinner) { @@ -159,13 +175,25 @@ function CustomTableRow(props: CustomTableRowProps) { ); } else if (header.field === 'disabled') { + const activeText = headerMapping?.disabled?.false + ? headerMapping.disabled.false + : 'Active'; + + const inactiveText = headerMapping?.disabled?.true + ? headerMapping.disabled.true + : 'Inactive'; + cellHTML = ( handleToggleActionClick(row)} + onClick={() => + useInputToggleConfirmation + ? verifyToggleActionClick() + : handleToggleActionClick(row) + } selected={!row.disabled} disabled={ // eslint-disable-next-line no-underscore-dangle @@ -185,6 +213,20 @@ function CustomTableRow(props: CustomTableRowProps) { )} /> {statusContent} + {displayAcceptToggling && ( + + )} ); diff --git a/ui/src/components/table/TableWrapper.tsx b/ui/src/components/table/TableWrapper.tsx index b2f7c39b4..4abb0f880 100644 --- a/ui/src/components/table/TableWrapper.tsx +++ b/ui/src/components/table/TableWrapper.tsx @@ -49,9 +49,9 @@ const getTableConfigAndServices = ( tableConfig: unifiedConfigs.pages.inputs.table, readonlyFieldId: unifiedConfigs.pages.inputs.readonlyFieldId, hideFieldId: unifiedConfigs.pages.inputs.hideFieldId, + useInputToggleConfirmation: unifiedConfigs.pages.inputs.useInputToggleConfirmation, }; } - const serviceWithTable = services?.find((x) => x.name === serviceName); const tableData = serviceWithTable && 'table' in serviceWithTable && serviceWithTable.table; @@ -62,6 +62,10 @@ const getTableConfigAndServices = ( }, readonlyFieldId: undefined, hideFieldId: undefined, + useInputToggleConfirmation: + serviceWithTable && + 'useInputToggleConfirmation' in serviceWithTable && + Boolean(serviceWithTable.useInputToggleConfirmation), }; } @@ -120,10 +124,11 @@ const TableWrapper: React.FC = ({ useTableContext()!; const unifiedConfigs = getUnifiedConfigs(); - const { services, tableConfig, readonlyFieldId, hideFieldId } = useMemo( - () => getTableConfigAndServices(page, unifiedConfigs, serviceName), - [page, unifiedConfigs, serviceName] - ); + const { services, tableConfig, readonlyFieldId, hideFieldId, useInputToggleConfirmation } = + useMemo( + () => getTableConfigAndServices(page, unifiedConfigs, serviceName), + [page, unifiedConfigs, serviceName] + ); const moreInfo = tableConfig && 'moreInfo' in tableConfig ? tableConfig?.moreInfo : null; const headers = tableConfig && 'header' in tableConfig ? tableConfig?.header : null; @@ -135,6 +140,7 @@ const TableWrapper: React.FC = ({ isComponentMounted.current = false; }; }, []); + useEffect(() => { const abortController = new AbortController(); @@ -365,6 +371,7 @@ const TableWrapper: React.FC = ({ sortKey={sortKey} handleOpenPageStyleDialog={handleOpenPageStyleDialog} tableConfig={tableConfig} + useInputToggleConfirmation={useInputToggleConfirmation} /> ); diff --git a/ui/src/components/table/stories/configMockups.ts b/ui/src/components/table/stories/configMockups.ts index 507032372..ad514b95f 100644 --- a/ui/src/components/table/stories/configMockups.ts +++ b/ui/src/components/table/stories/configMockups.ts @@ -376,3 +376,119 @@ export const getSimpleConfigStylePage = () => { const configCp = JSON.parse(JSON.stringify(SIMPLE_TABLE_MOCK_DATA_STYLE_PAGE)); return configCp; }; + +export const SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION = { + pages: { + configuration: { + tabs: [ + { + name: 'account', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'string', + errorMsg: 'Length of ID should be between 1 and 50', + minLength: 1, + maxLength: 50, + }, + { + type: 'regex', + errorMsg: + 'Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + ], + field: 'name', + help: 'Enter a unique name for this account.', + required: true, + }, + ], + title: 'Account', + restHandlerModule: 'splunk_ta_uccexample_validate_account_rh', + restHandlerClass: 'CustomAccountValidator', + }, + ], + title: 'Configuration', + description: 'Set up your add-on', + }, + inputs: { + services: [ + { + name: 'example_input_one', + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'regex', + errorMsg: + 'Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + { + type: 'string', + errorMsg: 'Length of input name should be between 1 and 100', + minLength: 1, + maxLength: 100, + }, + ], + field: 'name', + help: 'A unique name for the data input.', + required: true, + }, + { + type: 'checkbox', + label: 'Example Checkbox', + field: 'input_one_checkbox', + help: 'This is an example checkbox for the input one entity', + defaultValue: true, + }, + ], + title: 'Example Input One', + }, + ], + title: 'Inputs', + description: 'Manage your data inputs', + useInputToggleConfirmation: true, + table: { + actions: ['edit', 'delete', 'search', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + { + label: 'Status', + field: 'disabled', + }, + ], + moreInfo: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + }, + }, + meta: { + name: 'Splunk_TA_UCCExample', + restRoot: 'splunk_ta_uccexample', + version: '5.41.0R9c5fbfe0', + displayName: 'Splunk UCC test Add-on', + schemaVersion: '0.0.3', + }, +} satisfies GlobalConfig; diff --git a/ui/src/components/table/stories/rowDataMockup.ts b/ui/src/components/table/stories/rowDataMockup.ts index fa52b2a25..1a811c3c1 100644 --- a/ui/src/components/table/stories/rowDataMockup.ts +++ b/ui/src/components/table/stories/rowDataMockup.ts @@ -503,6 +503,14 @@ export const MockRowDataForStatusCount = { messages: [], }; +export const MockRowDataTogglingResponseDisableTrue = { + entry: [{ content: { disabled: true } }], +}; + +export const MockRowDataTogglingResponseDisableFalse = { + entry: [{ content: { disabled: false } }], +}; + export const ServerHandlers = [ http.get(`/servicesNS/nobody/-/splunk_ta_uccexample_account`, () => HttpResponse.json(MockRowData) diff --git a/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx b/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx new file mode 100644 index 000000000..1d63bd705 --- /dev/null +++ b/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx @@ -0,0 +1,152 @@ +import { render, screen, within } from '@testing-library/react'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; + +import { BrowserRouter } from 'react-router-dom'; +import { http, HttpResponse } from 'msw'; +import { TableContextProvider } from '../../../context/TableContext'; +import { server } from '../../../mocks/server'; +import { setUnifiedConfig } from '../../../util/util'; +import { SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION } from '../stories/configMockups'; +import { + MockRowData, + MockRowDataTogglingResponseDisableFalse, + MockRowDataTogglingResponseDisableTrue, +} from '../stories/rowDataMockup'; +import TableWrapper, { ITableWrapperProps } from '../TableWrapper'; +import { invariant } from '../../../util/invariant'; + +beforeEach(() => { + const props = { + page: 'inputs', + serviceName: 'example_input_one', + handleRequestModalOpen: jest.fn(), + handleOpenPageStyleDialog: jest.fn(), + displayActionBtnAllRows: false, + } satisfies ITableWrapperProps; + + server.use( + http.get('/servicesNS/nobody/-/splunk_ta_uccexample_example_input_one', () => + HttpResponse.json(MockRowData) + ) + ); + + setUnifiedConfig(SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION); + + render( + + + , + { wrapper: BrowserRouter } + ); +}); + +const getRowData = (isDisabled: boolean) => { + const active = MockRowData.entry.find( + (entry) => entry.content.disabled === isDisabled // api mocks are created for aaaaaa entity + ); + return active; +}; + +const serverUseDisabledForEntity = (entity: string, isDisabledTrue: boolean) => { + server.use( + http.post(`/servicesNS/nobody/-/splunk_ta_uccexample_example_input_one/${entity}`, () => + HttpResponse.json( + isDisabledTrue + ? MockRowDataTogglingResponseDisableTrue + : MockRowDataTogglingResponseDisableFalse + ) + ) + ); +}; + +const getRowElements = async (isDisabled: boolean) => { + const activeRowData = getRowData(isDisabled); + invariant(activeRowData, 'Active row not found'); + const activeRow = await screen.findByLabelText(`row-${activeRowData?.name}`); + + const statusCell = within(activeRow).getByTestId('status'); + + const statusToggle = within(activeRow).getByRole('switch'); + + return { activeRowData, activeRow, statusCell, statusToggle }; +}; + +it('Status toggling with acceptance model - displayed correctly', async () => { + const { activeRowData, statusToggle } = await getRowElements(false); + + await userEvent.click(statusToggle); + + const acceptModal = await screen.findByRole('dialog', { name: /Make input Inactive?/i }); + + screen.getByText(`Do you want to make ${activeRowData?.name} input Inactive?`); + + screen.getByRole('button', { name: 'Yes' }); + const noBtn = screen.getByRole('button', { name: 'No' }); + + await userEvent.click(noBtn); + + expect(acceptModal).not.toBeInTheDocument(); +}); + +it('Status toggling with acceptance model - toggles state', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(false); + + expect(statusCell).toHaveTextContent('Active'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Inactive?' }); + + const yesBtn = await screen.findByRole('button', { name: 'Yes' }); + await userEvent.click(yesBtn); + + expect(statusCell).toHaveTextContent('Inactive'); + + serverUseDisabledForEntity(activeRowData.name, false); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Active?' }); + + const yesBtn2 = await screen.findByRole('button', { name: 'Yes' }); + await userEvent.click(yesBtn2); + + expect(statusCell).toHaveTextContent('Active'); +}); + +it('Status toggling with acceptance model - decline modal still Active', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(false); + + expect(statusCell).toHaveTextContent('Active'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Inactive?' }); + + const noBtn = await screen.findByRole('button', { name: 'No' }); + await userEvent.click(noBtn); + + expect(statusCell).toHaveTextContent('Active'); +}); + +it('Status toggling with acceptance model - decline modal still Inactive', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(true); + + expect(statusCell).toHaveTextContent('Inactive'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Active?' }); + + const noBtn = await screen.findByRole('button', { name: 'No' }); + await userEvent.click(noBtn); + + expect(statusCell).toHaveTextContent('Inactive'); +}); diff --git a/ui/src/components/table/tests/TableExpansionRow.test.tsx b/ui/src/components/table/tests/TableExpansionRow.test.tsx index 9fdd0ca94..0dcca8b66 100644 --- a/ui/src/components/table/tests/TableExpansionRow.test.tsx +++ b/ui/src/components/table/tests/TableExpansionRow.test.tsx @@ -119,7 +119,7 @@ async function expectIntervalInExpandedRow(inputRow: HTMLElement, expectedInterv expect(allDefinitions).toContain(`${expectedInterval} sec`); } -it.failing('should update custom Expansion Row when Input has changed', async () => { +it('should update custom Expansion Row when Input has changed', async () => { setup(); const inputRow = await screen.findByRole('row', { name: `row-${inputName}` }); diff --git a/ui/src/components/table/tests/TableWrapper.test.tsx b/ui/src/components/table/tests/TableWrapper.test.tsx index f625a438c..f420e6271 100644 --- a/ui/src/components/table/tests/TableWrapper.test.tsx +++ b/ui/src/components/table/tests/TableWrapper.test.tsx @@ -14,8 +14,6 @@ import { SIMPLE_NAME_TABLE_MOCK_DATA, } from '../stories/configMockups'; -jest.mock('immutability-helper'); - const handleRequestModalOpen = jest.fn(); const handleOpenPageStyleDialog = jest.fn(); diff --git a/ui/src/context/TableContext.tsx b/ui/src/context/TableContext.tsx index 027fa333c..1416aa047 100644 --- a/ui/src/context/TableContext.tsx +++ b/ui/src/context/TableContext.tsx @@ -8,6 +8,7 @@ export type RowDataFields = { disabled?: boolean; id?: string; index?: string; + __toggleShowSpinner?: boolean; } & AcceptableFormRecord; // serviceName > specificRowName > dataForRow diff --git a/ui/src/types/globalConfig/pages.ts b/ui/src/types/globalConfig/pages.ts index b8539d2e1..5fdf3bc84 100644 --- a/ui/src/types/globalConfig/pages.ts +++ b/ui/src/types/globalConfig/pages.ts @@ -96,10 +96,13 @@ export const TableLessServiceSchema = z.object({ inputHelperModule: z.string().optional(), hideForPlatform: z.enum(['cloud', 'enterprise']).optional(), }); + export const TableFullServiceSchema = TableLessServiceSchema.extend({ description: z.string().optional(), table: TableSchema, + useInputToggleConfirmation: z.boolean().optional(), }); + export const InputsPageRegular = z .object({ title: z.string(), @@ -149,6 +152,7 @@ export const InputsPageTableSchema = z services: z.array(TableLessServiceSchema.strict()), hideFieldId: z.string().optional(), readonlyFieldId: z.string().optional(), + useInputToggleConfirmation: z.boolean().optional(), }) .strict(); diff --git a/ui/src/util/api.test.ts b/ui/src/util/api.test.tsx similarity index 62% rename from ui/src/util/api.test.ts rename to ui/src/util/api.test.tsx index 67f702248..5ae209476 100644 --- a/ui/src/util/api.test.ts +++ b/ui/src/util/api.test.tsx @@ -1,9 +1,15 @@ -import { http, HttpResponse } from 'msw'; +import { delay, http, HttpResponse } from 'msw'; import { generateEndPointUrl, getRequest } from './api'; import { getGlobalConfigMock } from '../mocks/globalConfigMock'; import { setUnifiedConfig } from './util'; import { server } from '../mocks/server'; +const mockGenerateToastFn = jest.fn(); +jest.mock('./util', () => ({ + ...jest.requireActual('./util'), + generateToast: () => mockGenerateToastFn(), +})); + describe('generateEndPointUrl', () => { it('should return the correct endpoint URL', () => { const mockConfig = getGlobalConfigMock(); @@ -23,7 +29,7 @@ describe('generateEndPointUrl', () => { }); describe('getRequest', () => { - beforeEach(() => { + function setup() { const mockConfig = getGlobalConfigMock(); setUnifiedConfig({ ...mockConfig, @@ -32,9 +38,12 @@ describe('getRequest', () => { restRoot: 'testing_name', }, }); - server.use(http.get('*', () => HttpResponse.json({}, { status: 500 }))); - }); + } + it('should call callbackOnError if handleError is true', async () => { + setup(); + server.use(http.get('*', () => HttpResponse.json({}, { status: 500 }))); + const callbackOnError = jest.fn(); await expect(() => @@ -45,9 +54,12 @@ describe('getRequest', () => { }) ).rejects.toThrow(); + expect(mockGenerateToastFn).toHaveBeenCalledTimes(1); expect(callbackOnError).toHaveBeenCalled(); }); it('should not call callbackOnError if handleError is false', async () => { + setup(); + server.use(http.get('*', () => HttpResponse.json({}, { status: 500 }))); const callbackOnError = jest.fn(); await expect(() => @@ -58,6 +70,30 @@ describe('getRequest', () => { }) ).rejects.toThrow(); + expect(mockGenerateToastFn).not.toHaveBeenCalled(); expect(callbackOnError).not.toHaveBeenCalled(); }); + + it('should not show error if request is cancelled', async () => { + setup(); + server.use( + http.get('*', async () => { + await delay('infinite'); + + return HttpResponse.json(); + }) + ); + const abortController = new AbortController(); + + const request = getRequest({ + endpointUrl: 'testing_endpoint', + handleError: true, + signal: abortController.signal, + }); + + abortController.abort(); + + await expect(request).rejects.toThrow(); + expect(mockGenerateToastFn).not.toHaveBeenCalled(); + }); }); diff --git a/ui/src/util/api.ts b/ui/src/util/api.ts index b0265eaa9..00486d481 100755 --- a/ui/src/util/api.ts +++ b/ui/src/util/api.ts @@ -58,7 +58,8 @@ async function fetchWithErrorHandling( } return await response.json(); } catch (error) { - if (handleError) { + const isAborted = error instanceof DOMException && error.name === 'AbortError'; + if (handleError && !isAborted) { const errorMsg = parseErrorMsg(error); generateToast(errorMsg, 'error'); if (callbackOnError) { From 0cb0bb70a5f1dc9e59cb32abb405ab6006e6f221 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Wed, 4 Dec 2024 18:19:28 +0530 Subject: [PATCH 05/23] test: added jest test case for checkboxtree --- .../globalConfig.json | 1 + .../CheckboxTree/CheckboxTree.test.tsx | 111 ++++++++++++ .../CheckboxTree/CheckboxTree.utils.test.ts | 159 ++++++++++++++++++ .../stories/CheckboxTree.stories.tsx | 2 + ui/src/components/CheckboxTree/utils.test.tsx | 84 +++++++++ 5 files changed, 357 insertions(+) create mode 100644 ui/src/components/CheckboxTree/CheckboxTree.test.tsx create mode 100644 ui/src/components/CheckboxTree/CheckboxTree.utils.test.ts create mode 100644 ui/src/components/CheckboxTree/utils.test.tsx diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index ad1e2568a..d5c3b1277 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1576,6 +1576,7 @@ "type": "CheckboxTree", "label": "Event Filters", "field": "apis", + "required": true, "options": { "groups": [ { diff --git a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx new file mode 100644 index 000000000..3f05e5a36 --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import CheckboxTree from './CheckboxTree'; +import { MODE_CREATE } from '../../constants/modes'; +import { CheckboxTreeProps } from './types'; + +const handleChange = jest.fn(); + +const defaultCheckboxProps: CheckboxTreeProps = { + mode: MODE_CREATE, + field: 'apis', + value: 'rowUnderGroup1,requiredField', + label: 'CheckboxTree', + controlOptions: { + groups: [ + { + label: 'Group 1', + options: { + isExpandable: true, + expand: true, + }, + fields: ['rowUnderGroup1'], + }, + { + label: 'Group 3', + options: { + isExpandable: true, + expand: true, + }, + fields: ['requiredField', '160validation'], + }, + ], + rows: [ + { + field: 'rowWithoutGroup', + checkbox: { + label: 'Row without group', + defaultValue: false, + }, + }, + { + field: 'rowUnderGroup1', + checkbox: { + label: 'Row under Group 1', + defaultValue: false, + }, + }, + { + field: 'requiredField', + checkbox: { + label: 'Required field', + defaultValue: false, + }, + }, + { + field: '160validation', + checkbox: { + label: 'from 1 to 60 validation', + }, + }, + ], + }, + handleChange, +}; + +const renderCheckboxTree = (additionalProps?: Partial) => { + const props = { ...defaultCheckboxProps, ...additionalProps }; + render(); +}; + +describe('CheckboxTree Component', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders all rows and groups correctly', () => { + renderCheckboxTree(); + + // Verify groups + expect(screen.getByText('Group 1')).toBeInTheDocument(); + expect(screen.getByText('Group 3')).toBeInTheDocument(); + + // Verify rows + expect(screen.getByLabelText('Row without group')).toBeInTheDocument(); + expect(screen.getByLabelText('Row under Group 1')).toBeInTheDocument(); + expect(screen.getByLabelText('Required field')).toBeInTheDocument(); + expect(screen.getByLabelText('from 1 to 60 validation')).toBeInTheDocument(); + }); + + it('handles "Select All" and "Clear All" functionality', async () => { + renderCheckboxTree(); + const user = userEvent.setup(); + + // "Select All" + const selectAllButton = screen.getByText('Select All'); + await user.click(selectAllButton); + + const allCheckboxes = screen.getAllByRole('checkbox'); + allCheckboxes.forEach((checkbox) => expect(checkbox).toBeChecked()); + + expect(handleChange).toHaveBeenCalledTimes(1); + + // "Clear All" + const clearAllButton = screen.getByText('Clear All'); + await user.click(clearAllButton); + + allCheckboxes.forEach((checkbox) => expect(checkbox).not.toBeChecked()); + expect(handleChange).toHaveBeenCalledTimes(2); + }); +}); diff --git a/ui/src/components/CheckboxTree/CheckboxTree.utils.test.ts b/ui/src/components/CheckboxTree/CheckboxTree.utils.test.ts new file mode 100644 index 000000000..4dff24f6d --- /dev/null +++ b/ui/src/components/CheckboxTree/CheckboxTree.utils.test.ts @@ -0,0 +1,159 @@ +import { + isGroupWithRows, + getFlattenRowsWithGroups, + getNewCheckboxValues, + getCheckedCheckboxesCount, + getDefaultValues, +} from './CheckboxTree.utils'; +import { GroupWithRows, Row, ValueByField } from './types'; + +describe('isGroupWithRows', () => { + test('should return true if the item is a group with rows', () => { + const group: GroupWithRows = { + label: 'Group 1', + options: { isExpandable: true, expand: true }, + fields: ['row1'], + rows: [], + }; + expect(isGroupWithRows(group)).toBe(true); + }); + + test('should return false if the item is a row', () => { + const row: Row = { + field: 'row1', + checkbox: { label: 'Row 1' }, + }; + expect(isGroupWithRows(row)).toBe(false); + }); +}); + +describe('getFlattenRowsWithGroups', () => { + const controlOptions = { + groups: [ + { + label: 'Group 1', + options: { isExpandable: true, expand: true }, + fields: ['row1', 'row2'], + }, + ], + rows: [ + { field: 'row1', checkbox: { label: 'Row 1' } }, + { field: 'row2', checkbox: { label: 'Row 2' } }, + { field: 'row3', checkbox: { label: 'Row 3' } }, + ], + }; + + test('should flatten rows and group rows under their respective groups', () => { + const result = getFlattenRowsWithGroups(controlOptions); + + expect(result).toHaveLength(2); + const group = result[0] as GroupWithRows; + expect(isGroupWithRows(group)).toBe(true); + expect(group.label).toBe('Group 1'); + expect(group.rows).toHaveLength(2); + expect(group.rows[0].field).toBe('row1'); + expect(group.rows[1].field).toBe('row2'); + + const row = result[1] as Row; + expect(isGroupWithRows(row)).toBe(false); + expect(row.field).toBe('row3'); + }); + + test('should add rows directly if they do not belong to any group', () => { + const controlOptionsWithoutGroups = { + groups: [], + rows: [{ field: 'row1', checkbox: { label: 'Row 1' } }], + }; + const result = getFlattenRowsWithGroups(controlOptionsWithoutGroups); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual(controlOptionsWithoutGroups.rows[0]); + }); +}); + +describe('getNewCheckboxValues', () => { + test('should update the checkbox value for a given field', () => { + const values: ValueByField = new Map([ + ['field1', { checkbox: true }], + ['field2', { checkbox: false }], + ]); + const newValue = { field: 'field2', checkbox: true }; + const result = getNewCheckboxValues(values, newValue); + + expect(result.get('field1')?.checkbox).toBe(true); + expect(result.get('field2')?.checkbox).toBe(true); + }); +}); + +describe('getCheckedCheckboxesCount', () => { + test('should return the count of checked checkboxes in a group', () => { + const group: GroupWithRows = { + label: 'Group 1', + options: { isExpandable: true, expand: true }, + fields: ['row1', 'row2', 'row3'], + rows: [ + { field: 'row1', checkbox: { label: 'Row 1' } }, + { field: 'row2', checkbox: { label: 'Row 2' } }, + { field: 'row3', checkbox: { label: 'Row 3' } }, + ], + }; + const values: ValueByField = new Map([ + ['row1', { checkbox: true }], + ['row2', { checkbox: false }], + ['row3', { checkbox: true }], + ]); + + const result = getCheckedCheckboxesCount(group, values); + expect(result).toBe(2); + }); + + test('should return 0 if no checkboxes are checked', () => { + const group: GroupWithRows = { + label: 'Group 1', + options: { isExpandable: true, expand: true }, + fields: ['row1', 'row2'], + rows: [ + { field: 'row1', checkbox: { label: 'Row 1' } }, + { field: 'row2', checkbox: { label: 'Row 2' } }, + ], + }; + const values: ValueByField = new Map([ + ['row1', { checkbox: false }], + ['row2', { checkbox: false }], + ]); + + const result = getCheckedCheckboxesCount(group, values); + expect(result).toBe(0); + }); +}); + +describe('getDefaultValues', () => { + test('should return a map with default checkbox values for each row', () => { + const rows: Row[] = [ + { field: 'row1', checkbox: { label: 'Row 1', defaultValue: true } }, + { field: 'row2', checkbox: { label: 'Row 2', defaultValue: false } }, + ]; + + const result = getDefaultValues(rows); + + expect(result.get('row1')?.checkbox).toBe(true); + expect(result.get('row2')?.checkbox).toBe(false); + }); + + test('should exclude rows without a defaultValue', () => { + const rows: Row[] = [ + { field: 'row1', checkbox: { label: 'Row 1', defaultValue: true } }, + { field: 'row2', checkbox: { label: 'Row 2' } }, // No defaultValue + ]; + + const result = getDefaultValues(rows); + + expect(result.has('row1')).toBe(true); + expect(result.has('row2')).toBe(false); + }); + + test('should handle an empty rows array', () => { + const result = getDefaultValues([]); + expect(result.size).toBe(0); + }); +}); diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx index f4999a5b9..c03df950a 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx +++ b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx @@ -17,6 +17,7 @@ export const Base: Story = { mode: MODE_EDIT, field: 'api', value: 'collect_collaboration,collect_file,collect_task', + label: 'checkboxtree', controlOptions: { rows: [ { @@ -48,6 +49,7 @@ export const Multiline: Story = { mode: MODE_EDIT, field: 'api', value: 'neigh,like', + label: 'checkboxtree', controlOptions: { rows: [ { diff --git a/ui/src/components/CheckboxTree/utils.test.tsx b/ui/src/components/CheckboxTree/utils.test.tsx new file mode 100644 index 000000000..e53e1aba5 --- /dev/null +++ b/ui/src/components/CheckboxTree/utils.test.tsx @@ -0,0 +1,84 @@ +import { parseValue, packValue } from './utils'; +import { ValueByField } from './types'; + +describe('parseValue', () => { + test('should return an empty map if collection is undefined', () => { + const result = parseValue(); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(0); + }); + + test('should return an empty map if collection is an empty string', () => { + const result = parseValue(''); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(0); + }); + + test('should parse a comma-separated string into a map', () => { + const collection = 'field1, field2,field3'; + const result = parseValue(collection); + + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(3); + expect(result.get('field1')).toEqual({ checkbox: true }); + expect(result.get('field2')).toEqual({ checkbox: true }); + expect(result.get('field3')).toEqual({ checkbox: true }); + }); + + test('should trim whitespace from field names', () => { + const collection = ' field1 , field2 ,field3 '; + const result = parseValue(collection); + + expect(result.size).toBe(3); + expect(result.has('field1')).toBe(true); + expect(result.has('field2')).toBe(true); + expect(result.has('field3')).toBe(true); + }); +}); + +describe('packValue', () => { + test('should return an empty string if the map is empty', () => { + const map: ValueByField = new Map(); + const result = packValue(map); + expect(result).toBe(''); + }); + + test('should return a comma-separated string for map entries with checkbox set to true', () => { + const map: ValueByField = new Map([ + ['field1', { checkbox: true }], + ['field2', { checkbox: true }], + ['field3', { checkbox: true }], + ]); + const result = packValue(map); + expect(result).toBe('field1,field2,field3'); + }); + + test('should exclude entries with checkbox set to false', () => { + const map: ValueByField = new Map([ + ['field1', { checkbox: true }], + ['field2', { checkbox: false }], + ['field3', { checkbox: true }], + ]); + const result = packValue(map); + expect(result).toBe('field1,field3'); + }); + + test('should handle maps with no valid checkbox entries', () => { + const map: ValueByField = new Map([ + ['field1', { checkbox: false }], + ['field2', { checkbox: false }], + ]); + const result = packValue(map); + expect(result).toBe(''); + }); + + test('should handle maps with mixed keys and ignore invalid ones', () => { + const map: ValueByField = new Map([ + ['field1', { checkbox: true }], + ['field2', { checkbox: true }], + ['field3', { invalid: true } as any], + ]); + const result = packValue(map); + expect(result).toBe('field1,field2'); + }); +}); From a8c4f687d9d5cc8116194a74d6a5875c499ee621 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:54:10 +0000 Subject: [PATCH 06/23] update screenshots --- .../stories/__images__/CheckboxTree-base-chromium.png | 3 +++ .../stories/__images__/CheckboxTree-create-mode-chromium.png | 3 +++ .../__images__/CheckboxTree-input-page-view-chromium.png | 3 +++ .../__images__/CheckboxTree-mixed-with-groups-chromium.png | 3 +++ .../stories/__images__/CheckboxTree-multiline-chromium.png | 3 +++ .../stories/__images__/CheckboxTree-required-view-chromium.png | 3 +++ .../stories/__images__/CheckboxTree-search-chromium.png | 3 +++ .../__images__/CheckboxTree-with-single-group-chromium.png | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png new file mode 100644 index 000000000..2ec684184 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:662dc2b2403cb24744eef669efc8ca055511a88f8f0a47c0126a2ee2c29d36c0 +size 16399 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png new file mode 100644 index 000000000..eb1277525 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f1ddab7d2e13f8e058a330a99cd190285fcd9834572726ec0cfee2c69ee8d8c +size 15566 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png new file mode 100644 index 000000000..66ea6e90c --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48c9586c25fc326bd82c3546aab1da8bf7af77a62ed1d5d1905861c43b5eaf21 +size 35864 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png new file mode 100644 index 000000000..2638b047e --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f4bef9b40b94d68d64ccdbd5ce4e589fd86cef32a01fa5f0be54264cac11438 +size 25038 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png new file mode 100644 index 000000000..3ed4555d0 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a9cd8e59c6a539044148262d32293b0d9017ce2677776b95f828d9be7129b47 +size 22491 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png new file mode 100644 index 000000000..07ff1fc5b --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4686285bb899c5d92aa1ddafce2e50349d0535b4c2bb5bc515b6dc827c54d821 +size 39334 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png new file mode 100644 index 000000000..716d844b6 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3e85bd398419f6c991b5aa6bcf745557f3b2f88b7c812fa658812e9dc2bbe81 +size 11471 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png new file mode 100644 index 000000000..943f90651 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1addd437737b3afcbb625878402194aa7a7b42d144019c89558ad8dc9d006d13 +size 14754 From 4bba2e1346973f53996b0ad22426c9bf5a86d1b5 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Mon, 9 Dec 2024 11:16:54 +0530 Subject: [PATCH 07/23] feat: added UTC and documentation for checkboxtree component --- docs/entity/components.md | 67 +++++++++++ .../checkbox_tree_mixed_example.png | Bin 0 -> 75685 bytes .../global_config_validator.py | 5 +- .../globalConfig.json | 75 ++++++++++++ .../globalConfig.json | 75 ++++++++++++ tests/unit/test_global_config_validator.py | 18 +++ ...ree_duplicate_field_in_options_groups.json | 101 ++++++++++++++++ ..._tree_duplicate_field_in_options_rows.json | 108 ++++++++++++++++++ ...x_tree_undefined_field_used_in_groups.json | 101 ++++++++++++++++ .../CheckboxTree/CheckboxTree.test.tsx | 16 +-- .../stories/CheckboxTreeMocks.json | 10 +- .../stories/CheckboxTreeRequiredMocks.json | 10 +- ui/src/components/CheckboxTree/utils.test.tsx | 10 -- 13 files changed, 567 insertions(+), 29 deletions(-) create mode 100644 docs/images/components/checkbox_tree_mixed_example.png create mode 100644 tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json create mode 100644 tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json create mode 100644 tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json diff --git a/docs/entity/components.md b/docs/entity/components.md index 4c1aa1a43..1f0cc3a1c 100644 --- a/docs/entity/components.md +++ b/docs/entity/components.md @@ -321,6 +321,73 @@ This is how it looks in the UI: The component maps and unmaps values into a single field in the format `fieldName1/fieldValue1,fieldName2/fieldValue2`, but only for checked rows. For the given example, it emits the following value: `rowUnderGroup1/1200,requiredField/10`. +## `CheckboxTree` + +See the following example usage: + +```json + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "api3", + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "firstRowUnderGroup3", + "checkbox": { + "label": "first row under group 3", + "defaultValue": true + } + }, + { + "field": "secondRowUnderGroup3", + "checkbox": { + "label": "second row under group 3" + } + } + ] + } +} +``` + +This is how it looks in the UI: + +![image](../images/components/checkbox_tree_mixed_example.png) + +The component maps and unmaps values into a single field in the format `fieldName1,fieldName2,fieldName3`, but only for checked rows. For the given example, it emits the following value: `rowWithoutGroup,rowUnderGroup1,firstRowUnderGroup3`. + ## `Multiple Select` See the underlying `@splunk/react-ui` component: [`Multiselect`](https://splunkui.splunk.com/Packages/react-ui/Multiselect). diff --git a/docs/images/components/checkbox_tree_mixed_example.png b/docs/images/components/checkbox_tree_mixed_example.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6480d5d994b6c05f446685a5582a24f3156610 GIT binary patch literal 75685 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

m2zY0G4i(Z1DT_rp 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%0AakEoDP&q(w|H$X zHI!w=w89pwq)r%t5wp1A==@^E3FG4WHFFBn+)ch*Ozr>C3c5OZhS zM*0kJes|xtLvd|m?>da4E^{48&v?}5m5w)LU(UkB>@yvf3kd~*3ZrL!WK|oZXA>T zsQgzrdSU$Z3O$h#3z>K^Vg7Gst-Y=e&x^RR?>0uvx&5i0ghNqz=rT`tl(g?VtAgP$7FG(zd-0ZgLbOdOgF&dm~D#dzc;G>u`a$ zw~M>iW=+?7KJ45A$L&GYNM{C_ELZWlXJ>iyiyw)I+tHk9v2|tpWm4C77$DyA*~YZK z*-x;z={^Tgh_R@4ceWi9Y*J&VjbH`76G=#A!>j67VtC1+%m&^}t-Te?zEY>HuE+t&B@w0%60dexVC zvj1S9ZqC4KlW5Co}Pv_^;Dw7Bc-zKCu8MaZ@4R??(i zp9tjDxjIymtVP%?2W*6JV{ZL_qvXyncsO;*zb<~1@K}6zXID zXXa<%Uk9UND0kv^$VV!r zj6O|#bL;faqJ*FQS+iF#u|vI5`z4=~i&T}*mV>$GcAKq|(fPG9)}L}sYV;lf89<~s zNrVUKxqg)7g3jh+`0thABpVaaLvI!F|t-&btfv!q0>(iFLWQ&m(s`DYMrSgZA z*^}ok;mhs#gIPqwo*e!5v^}o$rdOLstPRbfl9JjVH@nxVbp$KNjR@b?jCN330o8(nr(5EpUi89Yui5;gNd&J=I};uqRlz6d4KU$4(%2V6sh zu&q_b4hn}JYsz%jn?O>;hW08{Q{aEFfK9|Fh<`oTD_6(8Z;j}y|4mzN`^;<2_E(dx zrk}->ez4(3z*SP{JlD8!FBg|0?me zQP%OU4A%fP4nrV$ev#n16)U5e!Rt%2g^S2UdigY;zB&cy8i!NJCSd@kn; zya&Xnqg$f*Gv)(i47wJI)Y83UG~Qzl$KBL%3zH+c*L%*YF!km`3Xt00=+@>M7@EdW zCWV@6Pct1B{#nhX9RAS;JHx(S9*JtN?tmZb(y2|iyT&SZCHhFG zzxQ7RVJih%WA&up1YtMJ%9%w#GU@O9;HI+JIiNOC)Slo92zhH4m|X-?-gj4`ug#x6 zvg3@YB^UWN(C|im;9=s6WH1`S8khNG>?KBq2xSy}s!cd=ws`PyUlB~MC0oCbCLp#TzbDBGnmJ}>~xtgWN2bBmw$7-ZU}a5 z?U>)RQcPe2y_aV1A0m5&sGK#vqHh|`7Z+%7Xo5(WafUzI%yJ#YFx@56I5&K_wud$& z>&%xJpnv~5^Y*_Or$f9zvl`o%@yi|UWD_IRCV$ZJTKtE(Ydg;}$GXc()c;nR`?-kh z{kRYYaL5kL7yPAm2fTAcTkgxb6nEzL$+0UNQtb5EpEo0)y(YYVh>l6dC&VdwpX+VAH!HbSIo4+EMrr15Uu`rIc zM(KcG^`7l5ZqzmZ+|QHryLRj_=ZdUZ2h-3u%K+r(TdLQzM79_Des@;a3V=si%4fa8 z4cJSx_9(DgA!)vplqq{k$TA{55kl3+*PP8Lm-^yVr`X0GQx_=cbP98Bx zx$n1gku!dj_PYzFh%{{T2juXx$#DU)MGF?sARWv7sN10`dF`OgYA z*IosC-qA(iuhIINB6mA{`vcvcJOk-p+<4%Alpj6Q_s;1%%uCj6`=pOgW0_+0KRwaR>iq8$C-I9UJc=Uh0u7yEoMPWh$w^y}pQaiFJKKr#L7L5aV5 z13&NGzkckiDEZ0!xBvg^y)Z@Kv<(hVJ^NMU`y0(^|D2x417CD)gqryCI{)hr;F19A z;!|M_zv7<#fBh9O@4b9Y4SZ4IGv>KpT7dsCm4O2r0NS<5|4#!0l-ExmWB>TSjLe0T zmB2%$P(G>hzg(0Oa8Yd|bfACL@Bemra&tgQ_x~Q{KjzN=@5UuXu5IH*$nWcrkTcr^ zVx3#y()*qN7;=dy=UoxXW1OoNTY+0Q^wvryHg%8?mlf9U6AdO7Y`)J^!*SmvcM?AP^lZB8>L67oJw_xzU~u zHbW-q)1KQHd6ZB)D75o8CdSWowj4`er+}Ay{+c&>|@Ud7bGMM#Oz0|7?#;b zZGTzaW6|(xReNysj~6vu4153n60rpcGodfj)QA6mT)#E|t<;&oyf12c!0+{u4fgG5 zBNXwb%|oQdV5hiqkNDb50c3joqe4>Io`_q%&FI~;02Krc!4{x99i3kRa+MgJK!ziS zPkZF*FcYlmFG&!-X0CmIUh9`#Bt&}*J5;O>(73Lts;X+&c!_2KYGL%XES0}+=+xda zUx7<i`! zT8aoyZWTgy6eV+Op1XP!FveO?`q5AgJKHpt4(&f8XcjGjCiVcWnUepZ(5&lZo z#7)RD=X)3IhuPPF8sr|_8z3VO-)*4TltTYal^$r+|Fb*(T=U?Gt#a7$yDIIkxOD*6?31>6LNj->4Lm6!e;$ym;p0x~`syeI^m!`DLp6`lTZuzZx?w+gWNAeT|e)WhNJNghULwLlUcVkgoR1aDAx{918U~A zeXl6>77oK1?&^FR2+=ggW6mRi0?L5zwx9PjU?9ED_9?(W#>lPqLH0v=j^)82!C@)8 z4x9^FG*245{bvt3X#jkG_~wlL{%!}{74_*!!HI++gs^KUo6M#7GW*ShOQsYbz|_IL z6Ve`IS~sJ_syA8SIO;W2XcrTC&E(5gapgQ5BH=Ri+8S8`2rIS~ zFZ)5H6Hg_&h+O>y8iQ^+sv)_7at3X%=>^(Eo4lYbTU8OfHp=qZTSpXm!35~fRS~*A zHWb+t=(_C?dZljx;W%A?&r}86e9<(&MKM9(>XkF46=+7aXg3X}zN*aU1{GexHOOJ5 z>AO+smch2Zp_~0Ma!syL>F|bx3DC#*fq#f&2OOAAlBMm(s&xVHG+UT{9Apery0~e} z!iqyORJGyEf^O`2TUBC;lXP}DY zDBS9<0QHxP=+g4KoObB>)aza7HFNHJMM0YjsWrAwA+j?su7W5Lff@v295qaV%KVo)}>wZNWy@ zOFtjY*JQ!R&Q>8A7?DJ!SGDU&30>*o$G!5Bk9*HTW7m2`y8Y+`F2Aa|2;jE2*zylC zDZDx%ey{~8ttoR$5`3zM7CkBePu?;ePJ@ydn$#gB#l;^G6$^Cp8-ofO;La+UkIyC@ z@WWgh+@D*D63`-WbE7gZesc9Q(sAx$}Z|v z6DN!!Fq}mHrN`c-V<6ZBW1u$w)NLr}P4}OEF~C{Cr2+A?xx{w}6pnqq@L&kQ{|S zb%tFJ#fAq*N-_NWjsF39q_BNuFU6_lsWzE z`8Q)zS%~+{QIeqm8z<+pn$R@MFf}!!5m<=(A=vNe<;a}}K;(L-LgM^>MI}rR0D}O1 zM?kvb{D2v3xcKEPS`(gg1 zc6sVI(!o!EG*}Af5WdwShUT+2;%r4`MRta)@QBTt0cUN9mJ=b?_R5p#r8 z%^8mmX!8}O8)M{XH7(;yuGYS#8B3I_%oJPIoryc&1yV3>CCnyG=iDu`kH>dX5w-%e z+$}4U)`EC zIRG!yOv#R+6t2!#KRDo%S`ZI3FT)meb6S>by!V!nYvO*JR+X|sfSiQAYlCEC1tvz& z^dfumf~Egb*5duocxqzqz^GR(4{TQ$qt{#ia+qtknr2%cVQfX4=cZR##dclu!RMgN zFD$>P+#HASzT{lgwmmKeOI6k(BiaKu6ETjW%+S|M&u>hJ zd>5p(?5qln$tULOy3kJak*B`}(Sozh7Hq1BhFvaes`D^e`NY@1P!)SO@-YyKah_FT z`ArG?NsPr`s$s!0Sn+Yfl>r_vf3tFg1yGagnOKD{&2(tGg{>Ra&pa`)aZfOZ)-2k) zs`I)mwu?7;&X!!~HQ*Ztj1lu`LKC@Z`Qpy^AZpf9AIQ``htu4VJx_Q!r*jz8N}?;4 zE9gjpt#9g2eLC7AD5iPLO-%Sh>Ba)3#LB%UQ>Xb}_XEXYUqFU#Dc4Hk`8Psr zPVwCagPIUn2hb(mY;RaXH(SJ*JVnUsV63|+Q3ss{$6+WAp{6c*vNY;hZG{P7CmL}K z@T63U(U>V8%9_{QxC8n$k<)>#$U1Zo6i_Ws_KK}2xzPPp zbwD28QD-N?dn-1{yn!H}+?AzUTq(8bLEG)782A0|Aqo+KTQ6k9;?^pdO~duu8L8qH zxcL=#Yc&Dnf~WS3G5Q@P2~qiDEmx$ua6g9lFM0CqQ(XpR01=Y8(i{RbrP`a(CT&w{ zkh|fR6#bJX7TG3!QD+2esiOJya*K*%OC1dv&jHO(qOsLWyGLgerIRBgpB@UW9BrOA zEt!Lndu3>aiywyKbpbj$f5W};L47%$fFyps{IWo>cV>jqREt{vq4QRWL-%OnBjq5Y z_}9j{Zl8`?BD->O@({dEG_fh4JJJX0Y9JI3s~KcwMRzO?bYR@rY9OJ{V0o5WC3$i& z?n~j44daRKuoK@Vh|vfkvVuqEDnQFEl+0>eTcBTZe2iW7bmJkO&+>c zsfoJVB_cW98WoXLLt4mmQ)K7zkr;AB>h#hXd+Wx5;`B{uGlt8&2RlxqxK&EjO*7iT z`mJdU7@S=U#$or`!>4!~5I4>Yx!i?Wo##>v8LI-6fz@)NnMw9)un1s|SiAOn>vtWb zh88Kdisl)r!b5fiKIa``B|fN+cdEVw!C>n1FRrxI$;<3gJjz*H@Y$9Z0CLemF=(`F zb$->y`M|aKQppWoYG~@?VIkpZ|Md>{?8BS+^Dj13PY3|+mS9@96cx2rHNi9NDjVe- z{&`Ih!LU+Z_MtIpjo%?8kZDGW?on0fu?68l#B6Wu)FbSr^x0%{S?}GC#}Z7uR;k_B-)CfPZU$v?+4dFG zjxHZwUSeKv1Tb0-wh1hj&f__4$DITKw!HYHDRS|ila;!kO6GdEt6d$%f8ECvr^+qT z&F6l>9B7hl6|aw0P+IR@7-7faAKc^ws@YP2z6rioQ7w8H;Y9%ks-vcL-N33N*{kKv zR#pD7iJUnnWvh0pGOTbAhaHgXeZVLL#g~Xb5^)GarWS93A3rj+0F+?di%rY{naow) zZpvqS~?;m096xIAV_vn0Wi-UOisrGLOMdtN(dkOoqy93<%8yT8C zx9WY_`*<9E@LU;>tjdi4M(}x|2>_!0R+(#R*kjbIT`%59tfbe!-gpaaK2c*Cyx?1< zmFA@@xoD_U^NvkgNe^Mb4s?*LSHrMhay#D5U_#&t5@|zOJ#B60P{7;NVu4kR z&%rcDMe0VsHd2*trPveGDpw)!6z62F8#lVwb*#j1u(>yQZs8v~v!UrF0?81VBM=Hk~& zw}rzBoF_9yG!y5Xy_~?O%mj$>EBdz7+oOwZ5CPqGx$ykpam1}d{FSgbo^f)h@*@x% zA0N3E{VL)er9@CEwwJp#8nK~!b-ZkkaEO$tu}_?uN-J_8xf84?w_}OxLVF@hH%FyJ zS6&(5zmT75*3k_8!PuU32U4KN+!^w}Qm`hw|H9bX&yi&~HFc$_pv37jK# zc&BDLi3rsek2Yg4)k8L7b2$O5ptRjkuHeEU*GLZoaQOH=b>&H~2ABj>qe4Ufrd z*0hOE)@Wzc)!V?q!b6hk&QuiQ*TDS5Fs&iLi=&f|vYge~k{`@$>QVcSL%2R)e5LMk zVtU2-ux{_n06{xcUb)`_pvp8@e-h%wt2lip@ke%-(kkn*r~~Es^(znI`R||hkmme5h%U^=&&zSgq?Z_Z zmY3>%1@EV%orD{l3>KjHH7c}EY*thV?F0+5OB9E0FcK|olnp*9^68y{tMcD$cj zZ!<&jP-2{<>7hJ8lYs}wC&Qd-cW?YSxC@k6(A>w$J{U}$pT3W&mH=l~%!HtxXI5EL zpeMFFZyu{zc=E~BcemGMQk6St+@R!v6%hsm2RoJv)`WGOr~5~prH1B%+Krz(BW7Hm zuf9oB4$aJB$rl1z(OV)JCMQ_?)_w4lFf5z_c;|-jytoQ)gxDwL815Q|)-4=paR(sH z4v8KYVL;Lhi(beJ5RQBGNnm=U(EG|`wFJ7c&ZZCrX#*{eaa^E!NdtY z`gtbimW^kn9F!^Z{9d%*t*!qi>>wq2NYwQ{l3yuM~GRKCic~jD92c=Ar}^kdRNz z=ruU;U{Q_s;Yg5do@m{LeJub23-&4$J?kJ z?BnHnHq)g!F=PFS4sSGiG%lOJ>vk`?47Vb}Xw7~|^XlKaFi(S?<8~{XqjkD@erIu2 z)FQ8OiAd8!WR`6RVB}o0c#GuHKmt$#t^E#;F|3XiYGqYa&pS$O?(105*>#^ESU%0k zi6dkmq8ie58K|5O8sr+%HIx+i_Tbz>44X&OrFlITOJ zROmgB=;g9-sbCN0$Q;tYMS6-d1 zNd^t3AN0hl_1>^36>*FtvUVQ~0uY-`WBfQV@WsVW8BRNKJFm1N%TrI_a~SGb5+KmM z=DCByP{O#{BVBXxq!Lr#(qpH*oluz0D$B4I)%o5M=gT<0`C5I1o}z7k?&I=M4&gES zTNyb|#jLaQRU3B&nFJo)$!6cJn#d7E|7k%L=>v)L7P04-8NrdM8^6R79FvD&!&%(u zZ(87Jmt@~V4);e1N9V-Uo!3#A-9dGDg3tTn)>?gc0Xj*G1vfxX0}@A`AOh0wInumf z6IPK2W9L`>a&HL3MIYwy8hno(--$3zk{*M<1ptcfGMY<=m%(8Q0Tn=-8-JYY7OnLo zPJ!V}`sdc!X}!T(mTzTv$E8kP9}h0S!%QpVF&Bm%jei<2$l&hLLsKB!YiYS41DLRu zVdA1ynJp7{+VSq{uPy`XJI2Wq!Qz-gA(%tbp31^ZHb2&}l*Kw_w+yGTo*)JS$}R z^Cp)!2Q=;@@W1wS{KkWxKxE@S`gXS*!fB?#WOuGnHf+p0g8uELOLR}Z3Az`4Vfq&G zL{ah19W1-z$$I~qG=FNeVvU~MNv(84>r@q{!)I*9Jgp$N-BruvE>tzcmLt?%S`s9X zu3sj}mL1~8LNzTSHYG`2pR+o_@rkWTg6xqk_rzmy6D&8~ zilI*g?pLNB6RK#D0Vm4c)Kw1ZsKMny(4RtOR;#R2N2@cVK+7L4lV!YIQoKW1eeMdD zs=A(PQe!_}DO7TA8Y{e8Jj$KEMLWZ=|KL4;4qycs#HsdQhne}E+(0!^=2X4XT#4M< zVbqzRX$72@FEcB#Z(VG)3YXXFIl9wikpWpfuIh=ymUoV2J^-;q zLA4tN^=geI_)Stjk3wbzqGQfnG2nZ*u6P-TtIh~DcGuNRww+asSyM!$I*NFIxUXBK z$74DsQur+6?Rm{BPZ7)G?zDsJLNzP~Y>6G**WPigA}mPDy4HHV7pPEGMF?~ zEM(5Ho82l@_AQ8+G)^(ZRXce_xoEoWO!{}z?c{vYQNZ8PSU>zVS1RVEF$l83$QPP? z{l;>%*WKF=J&Xr5Wk6l z+U+l{*q|Hc>hs|uk4zAAd#W^6Kq+;DUB)?XoH=Kix7Tv8y&N-%R7QQIj(b3Qt5!t> zdlFoUyzD+zw6a9N>g`4^vcz=Ds}$SRz)&a*N=gm=+vb1e==CqMsw-?~e^p?$P2~>y zPOs>9dsl1iW^sKC??NOW|_!8fK8#riDpomO!DV_vzCUkEuI!C zJ-2({rlN4#pfHLP0MyvwG0=E+?~U;AzS|cE5gkXThj1_qyB=aW#5pvb{xu-ssWf24 zux$4&1Z-=_j;B*hJZz{fShMZ3IIvl*a^aD@3w4u)4_X~%2mm93q^Jw7ZqAl?$&+K<n8}c`XYp_8pz1gN``E`tRJCg7y3&bCP3$j7;kTFE{hNl zSmqDHOg=v-+<<0ReCmYG4ayxGyRMY%R z#~+&0+xgSlV-LXc>cPi5@_3kD%WaMao=tajy;ZE`Cb^UBE_4`$#iYQg3pwXNc-nTQ9 zf>~-^uOPtLX?A1Pgv^kDIgJbtlAZRCAi=8dh}s6}n$w``ZZdJ#DiXZCGRxFrqdt)o zk)5V6gGm2H%`wT9&KVf|+5tsh=k`adWswyDj5Ej}Ak(ri{Ifam5mT zdalryk-D4prX%Y6ulKSaPwX4iMUqSLpxobghe}b zVyw2z*V*^G7jhS@_Sk{ghDZCovb|O@Pde4b_AsRt-~o5Np`&2)WQ5tow_RGRs4m$e zXr}6HPjSZ3pZdj-4>!If1Ad-=JA$^hS#nukUYr_h`i8s^1FIwFJL2G(E`)Z)dxvK> z5Wwx}v5wJjvTQVBWftRw&~tYqHE8$vNBok=01lU7(PP8-k zhQM*|>}F47;zo^=?YeQiLpec+F5ra2@06uS9f(XSjh#F8MY78tnsrnY+^vv-WS(7t z{3{0~ZQWqWNil{EL@?uhR^30{fS2vM+De2AEYK@ydaV4v0Dx93_JW? zm{dZ?z{KaR!=&=o{+SDZyJtXJIdS^JxasG7=Dh!f6`d*9{Xxy(w`BUi&S3B5%e%l; ze5^DQ*K)S~Q|+Mg-8KOXy^<#_;%+_C=Y)W4O1|8Wz6 zf%>{9PJ(`!@$dKlobwqzVB}sdxO6}K+avm~uX+OLaD*;>(f*%5K^Fy#+--1$^|k$& z>EGiIfW!B=+wPBf|L0Hq-|6z_zWnXC|Ien&H#%KAo8NaR?5Ur``V86-7gEIA>swF? z_&%WqB6&cVsZCvlVi$7)P->|3jiUXH-dUt5XH}+Rm92LtDLW}%sN%fBs(ig)?a+|eA<>;Xc zCN3`p6#{~X$16=;e5q_R=>9nY{T}SWp0bTpJND_|WfjkRwm)6DJcyXg4v=ruJL<4% zy8SOy=JV%(D^91w@A)~UbzusN8M{nMi)RjDA3j|Z(z$y6wX9AAj2ao_T?~^qP4nS;G7GMfl~N8T{;=Hm_S^t+hMcvM>l`JqBp} zBg-B)(7+*sLJNwj%qIpc(Oam+J*d-0r+|JFn=n26W1Dc-?nCVaHkBf zhkF*}E+aB=3K5e(l#YX^j~#VUGLcJs<~mD$g895C4Wz7w>z%Sqzvr0E#kwQAC7;_8 z{%NZc#zD2WKks=t3m8;hbNS*$1qYgSWxKWBXzJM{3$M!G{?kX&Q};HU>g)u@e>~q| zuc!{u$~s;iV>>Ug{^Z+RgB#2vs3W^Wt}{ebXYtHn9@N_)OFQEd`X>?P=k}|@1guC| zE>|V?0l|N5$`^vElRSt7m4e-{0Q!;!dK)W;N99FD7A=Daq(A{Y3EC|~1mc(e?W0@c zDav_}&YYfyK|1>asSox9Q?vD>*Z)Kjdx!10BNuT4xlZAMl>v3Q;=Mk_TP5|TYV*Wl z_sLCZY^SXL@b*#0r5tC*d!*|&u5lw561e`lK$|_8i(o-9`0qVOWju76-=4B#>Hu(f z^gjAX@MJ5}WVCyh zNSwHnCD6-i?X4ZQ-b(P?B7J#T=F-oXqgR^UkI+Ze=)bAjHHDEKa`X{pr?1|92-sHd zxW_fQc8qw|=Cg=q8>Z20<^e+c&WlI*2N0%CvJ{b4c360nfZ=!dv8*?`*$;2zcHl|x zuc|={3SFk}W=3A-vm3}=U3zboyZ&f)c*Qj{c{puL+_A4N)TCqVfN?U|4rOpErd&$T znBpP$t@$qVPMb3;DiaXtB`l<&Jy`2y_2_OJ<&|Z zlSIAqk+T$vckujYNl}}rd>EE=)r17D1qFJ67r%~*dy~LeYNXHxB14b?3dH_-wWQzy zXZb}Bi*Ijb9RTA4%DE!#ZnBKs7MqoE5833&?)P<4RS(W7c)e-9R!g#vBkJxlhftDd z=%RP7yRA1Fo#L9moDeT&gGH2=b=nao8k2X(X-l1wRj$!yuuls0WCaG9F4~<Y4kcWV56aSg=o|CHpi%c= z6E;Hb2rA&!@lvPS#4`q&3@q|!cig~0PGgi_20*Y}Ohs0&tl4c5Uzht>DLl*{kN4lQ zN_4I)mHp9G;8BbhYJ6ABo#fj=+mNQeFbL%T-}66jT*{dQ2^7VYW5{(Jo;$qg8bw!D zk6q)fuCT_X0Il71zs-U?|Bl&&{^pK358Dy56>m--Yt70B7wCGFV@Bg}PsB&NguS$q zWRpEj#4#Zh{MhoL5(Zh~3-#pH00uA4&$}}2vw7l{S&J`$Ov%^DYPFZ9Mc%hBJaA}w zRB+|sMnP|}(DC&w^`}xlqO8nK;;oXrn@xPdFBl4ts|)M-hpg7=-vN(k^Q#)lD<;7F zbj*wVQf2neEsYJ53v;e)-nyr+79OZS=z!QbYd~uis1xOb_m~qL(3V1r5PV+vG+nKU z+mL9jG-r+Y7nx`HdB$a5-8!kt64{;mRXgvGT~55!y|`h3Cp?!A9khmEJ~3X31ZMf5 zB6Gr|^m5 zIbzAacLV2)pZF%Nv_rP$7&S@snOIg4$G+=ov>L6WkYd3^Nn-o*vUuJ15YjLVL$vq5xppG_DXo=5tjIY)Gtn--^chj7wfrqKEOSB~G-ycoUHe!Jv0g00SShfm7r8 zm}r&Q-3@|(zAb--BZEmqR&>E*g~6}ocv*O|y;brMZ_%}KhN5oiZ0*9^O0>zl@0T<; zoZp^l_Bv7Q(DKV?{G?pUeSi-k6d4n&^jqnNz}Pjn%2DI!mWGC{O7eBp*cB%_toCGV z#kV?0I}q=c*CM2)#%0i1Hj~$)gpAADyeco`Dc#Gl{_4>lCyK>mm}3WcIm-rQ`*xQ< zq_s2UasY*{aF8g`WAZx;TIZe5>IX$Z8HV}9ZMT8G=WBaL86V+Q$tuE@x6cKmRuLAH zdMS4O`jH!^&?WyOWSsw77Hak!>%M|vc;$nVL?!dZqX&Yo>7fC2YqME*3G?Dneq z?opT~QJl+ca4q!C(+$)z>yF~s)BDiGK}>63I5v^xL_)E57^nHP;JW=)%-n z-98Gpu^>U;#QU-f=IgNFz)j z4Z!+(R8t!k#lz7$;+0~6d1!`HUI;TTD^TDTyZdiY5#2jrlcsXzwLY@HNf#^~Hesje zT^Yn|?i7<%%!57jx}QeV-CTgyHG~eEF>f$f1gnvHNZ--W0#jaf$7c~nW$yKu`#`ui zOYswAZ@gADktvA<)M%S-t`T6#z7tBbR_`kNRY*yIMyc{9|8>$5Bx32MzDY5TTeH+J zfD^)gLVd@2=YBa=Jk+TMO>5HM+_gBoTxOLMEhD`rH?%a$pj4qX*g3Rg3M(EHA6*X3 z3weiCyu7{%T3b=Tep#=4D1b8fn0jC#53*To(Ml;`HuW3@vBBQ)9y#}xDibn>mc6_g z!b;Ac&ji4KM!J8IdevO8(;V%l})3TWD) zFRSITvx_>kU$(A+oWN3>)iZQ4O7KD?+}CiWiuDBLInJ6eKs_yb+gMZKP7E#0@sPu$ zP)FEjFc@Ws&(lWpSlrtUe6`KZ`$dNCfHE_fW3y&c5Y{=Koh+{qpZOP|dXuO&WyLo`+__g0h1F;~PF_TUmcM!Lt; z*qwEZl!uL%A@E4^P$^1@sPkFx3JiJRY{p~{qlVXARd$HJQ4cr=v{+Uqjr|=H+w&hx zdOV_6)b~3}KXc(=u#&!3K~?cF!9YNahd0GDOKr9{+u|7KfnJrl`)9@<5ScPw&7(=f z*Xn&8ik;A6GCbLxj}I5g%HWW(Sx8RnLFwp%grWXov2IlX%$#woex^1<8GPej)|)O3h?SF~e63f^YY!>U2QSy= zIb9h8fmC(y> zqzGh1Lx0CbR6;-*r_S^e%kPSjeU03aOS^?W6?rc6H;b5qBRS@%TIgDoAvA3t2mzRq zm&cCtpJdMAVfI#m3p;py-x}7K57>C|yd_pp6LthkqK<_{3QqgGc5bjA&C%awGRV;C zkp2ATk!}b?qBrU`kDk^A*(n_AYyN6Wcp&>}*M5#F+*yNsZtC0Jt5%eiv|X=ANu_hs z>;r*#GTts89*%xY!s6&|m$x2ovoaM&tDno^zWAWS{qi?+g3YL>Ad}V+|IT+ekEE?d z5xI?&9T2J9!VmgvK6cN-@{Q(uL4=pFuydoRyh+o`N7;YFM#8cIlBQ}uf2-$jMw1qmcgaY5WT;6R zUZ{GCFPTm;ZON`mj@Th79vn|126Do-N_h@2YrnjsyC_RpT8njhCb6QMSw-*92)Mez z^aNM$Kf+ZgZ+{;0p4EU?!09zwi;U)9-VW2S@%h0NT&cIfM7`NC>4ce zG(Qz8Qe@;ezbfc~;6CzTW9oTNTvNXKqMmQhbU4m;T=L+T^mY~lb}nmrc3R)rr46f? zTPU?alxS~-lBtluZg;SkKH!X}F+zVS4`VT#xKU=%ZN$JY9I(7_Aqax(jExDOQDORY z`KlhrwvvPqs+~TeLQd1k`bsURS!4piHPvnsc}YzXZyYpwGpVDgRvioxiWD#uBR$!b z?ndT(plzPKE&c6OVl5xZktjYn{kVY~!Ch?G-h6Y_Y0JSJst| zWz$8EUiM4tjd|hl_MsBXhZX5^JZBn+-xd{;D0a2)lx;_w2;Z0D?!fn0Fi%Wmw6T?Gq1m zB$q~i#G#9#VWx$nl*9eioY#$tpUHkj3ll*$B0fA5zWw;?PPZR2T`lGkkh>Y5YTFYbx^n)z4;c7hTYl8 zFXEJx#Wd*fu5P`l4Y^z1?}K_rpW}hHGOb+{l(aH^3iK>Xs&K61vzV@@KWIO5yR8}G zETaJ7u3u>!kJ(-+b&&d_#D1lvuI%LfXX+9)OdGpX>q1asgz3oP3>HFmYB-Y2>Gf44 zd?pWyz_*I{S@N`xe42ZBIkthg!oe7wq(okmn5BogY>MkOOoyf#HjE zTshWPO7p{&0Fa#du86WFh!ac)*l=8Zpr3+i5Iu$?IM`qB^NmBuYbsCaePKIN;Sht` z92Eq0`o@W(Yh$+)cY|M@QRpVwarmhTf@JrSJZ=&sYW7v-2~-|>1E*=iCDoxeN)-R` zNPKk@P;i<^cJoAo-4u7@qhdgVG{1*vjw+s#{6OA`cj=GP^`NcasG*2BFj342bn)X{ zVCdFMeIpq_5UaOS8T~$^HTO|JpUg^*8Lr2o)rUe+6Y4MxbDqsq-+3Gvr<|oN5+{WK z9>O>!G2QGaiEI2>RpX@zAh>z#vAtBIz2a7Zw#=*SQED;tm~1?2}O`KzyjB`~x| zp%FKBi0%8$+7Rjs-0b?UVPDm3(#a*YzU0cSVl~D{D97uEn?=8MV0rAR6ptAO*a+>% z`P96obr;7a<%s32R)Ff~-+=)tLG9r#Pd8<;3&U!cGd-n})`A=IB^;g?-d(lXh5bN+FGYt+E>4M7ft& zIpORU>?IeP8p`+v5j0#sAiDYXN{xx2)u$(O?j_1x9{6#sHPWA7>T#rn;!I3;EK-ts zj`i~asH4UBcq4X7!wPMozuDrTgUQMY?kiONod9X81B+_5u~x@#&(J2Wls*u9rxZp* zYU!T0+g$I^fTd5Kk!SCmGxZyVqw*6ry;j2?5Km}pMO~`N&hX*z<;^i#&X>l}XW9|T z*;1r0*JU3+dulO5JG}!VRxpn?VNJ;($1giSf zi-|fP(ti9kY@^v~E28xxM^5!Fh5>3D_HePxJ(dg55ww=}JH>S>BwzpFEKusH7t)S5 z&)}Art0R+%g@PYscY@2wR_X(+i3-y1w)Cw!a~d!=slP;h&&pi%XtvWb(~F?ZnM-<2 zpBV@m+r=pxfL?xCyHhRR**3&+PfUq=U4cw@eUUf%p`vg5$l(FL%OffzkHoVrID~f% zc@=)u?ZtMkL9x2SKzfZ+H95a(c9zimNz|{zgk>rDHT|nRvT>IA9Yqx6_PY?N^RTmx zgSZa_<=M4m@3d5AMVTC(g+ma;r#Z-{2T~PG83^reBvV>rNKjP;MY_B90N(_r{4;Cu zZ{uU07gQCyF*+Bec;o&uso$%)7pTg~yHPtaO?f$dMQ?SG(tH+e5yzvS%rr6}jo(p247&=^uQv`ho*zKOkeb%3li;A-FL$boK;?@T?%#)uFC%&- zldY85yx$skAl4o&S)v`5aCdp@zf`j^9B2EXb0;g}c7#uk&dQHg?``_G?euKs z^=X#zJHgfC}~qS5^ac9iI6SXW00M(g^X>gF=U-kgoLthW8WFO z5R)im-}j{~gE3<_nCP*$Z$yzbrtE66FwQVX;Rmb1OUa=DT^FwI#|)=N;$xTny$9v=del^{C$ZmFf1u zit^xLH|TVvd98DpU&(?$*VU?gN2aaH2x)rLg`%w_Iy07;a2|I=L2jBNGZOovR6y2)HKx3H<%z z{$M(%^=E+IV&B;(-#0loMCZx9D}Gg5AYvHA+)8%pM7?zA#=t{Dne5A~J7!JVMnvtE zMdoz8!_Tr*NmzE`K1yuqYCcdId_llnte|`!B$4$x;1N9O?0!q+EEs37Of|)2{&RmW zzMmT}?+%ZfcC;VA?l@4a4L&n7Ru2+g>H6M7%3Y~wBLl_KQr9{#O1#u!Lm6v0Yd_E+ z2*hnKY{I4vUDeG@u3T*)QkdOhi;}8F=iGaL`g)$@gYhYaUV{XRlU}XjT`GiFDc|J@ z4;$;R*&czXUy}M5p`ERB;o7eq1d2Qh(CkWUN||`L{i;3IX?|#mnSF5+S552UV=c}; zfx6I#CfZf#QHl7xCxPNG3Ke;^b0>P)-KV8ex%X)lq5XP_MU5p~eg*F@0k9YDx@!_! z;UGR7YzF6-L;6S-d)l!=a>tTH4w^F4X>okQI6QvuM0Zm2L7tJv91v7v3EFv3{SY$2 z*4oX*2;0f{W)tq(?8ce3pD@boL~}74v{E6PLzsaphmmmAdEVHV8D~nr!?zSL?7oft zLY2a}cW`i9yXc!DalM{&6R&HSbCqBfWM~JbU5czsc+9h;?h`cS_XmHTQB#q$$zYCK zFWITCS*Wkw?w1Mta>n#J!-^2VRfVruJ|mjcnIV4f+`Nv<+{o9ih7?C;UA{gP5ky#k za3d|fhM@`@3#W9sK#>-u!~Lxi0SRh2RMmROe;{JMat8;@&l(+%BsniU0m;U7H9$p6 z)*MrW>B`|^HiB((5^vY|Y^3QwWtp?Ks56I^QqSZl8Aw(r?t_-x8VP zmG5XWf4>voy!~JKi^Iu#Y5GivJ09Yr(l^)<$ ztE?2NIOQm0iui`)%N}_zst3T?gmRiDKT--|`AU;XMeGb~)&lUuzc2+^MwM3GR>+I8 zAKzpikOuKQ#E@E**!9Z;2CC{)ZheTBmFLTgcvoQG&)@t0O36sTiWb|7fVb>}v$cs> zfmY?FOqXPboT$M{->BuWQo18U58JTHvFF-hk0*+6D?U!@(u|7Hisdx_5z>Oq-59|~ z8M=`tdY;mf$sd|MeJq%iab=J) zkEVL(_kg*EI{vsQwLC;sw59IItUNtTY9fU!Y?~w?Rrs{E7%@x*m<^a`t~Ee7dUn%7 zFn||(LuTmM(MjS&(y(r1kA)jJzdElt#*U-P$fBL6sGmeFlnHNH+>IS`p-E1HtHKm+S6wTm`YxyjuVTVC!{vTGN1~NM1N| zkJ7g$IFX-b)6ik=VAtE2Ewc!2NcoYuS`JQI2oO(-5dve@4N5K;Yx za(UPS=plrDgZg%OogA|VEB4_gGP#Riq{MIF0l-w>bz&NDVmF$#Y_WtfPW}8}py>PX za)h}=4cM{2zXjXTzjX@`d8>BJ`Y@+4EvbxtXR-SXMV3{C$}s}n-i#cOSQRmoibG_8 zt~Fn?>kLqRNe+{&IIvdSb?mU@E2b2W2aHnV+dv=>+smpGM%pE6!x}r4*5r^cF;2_47I!A18l~a>?Ew51 z0K-}bZO0#7oxlB;zf(Deh^;1ogOug5-wDTQo4-QCP{CyO_tv01wDBlc(VsPzs`%k95B;GXTsZ!we6da1Mj^IjSNT_xmE`1{@Yv~9@7_O2v70pt?* zA0e}Q{^7suMSe?H$t)fAXvgk$cDY?-IJu>(R2{GJUv!oDfnGAVbnKIVMves;1K0@q zgDE2ayv~O$T_u*;bN@wGi59?ffo}11?>Lq1C*3*!prgP#4XT)kzx`zAa4zcruh3H} zr~sq7fy?p5)xSNUJu z+`p!>YaqLy{{NYZWIk=L+>U>f{P7=%-}7->Q~ss zN_a(eXX%15(fVZ*v*K&564p6n6Wmi+rD_wG zTQ#-H)aj^$mKD9{waV=?KIO13)IS!4EZJwxyUUf1_Fcj|WaOEy4H?<=b2O4OLT-Qu zqJ*rYu#o2njAHGDk#a(*98V)?>F}?p30eLXIfWl znK8Xf_CeZ?T>=gAU6SRR2&sEdeR8fhp%*~Qj8|>giI#fS?76#x?g3dc zcgu&U-`A_LDE;U4*=X@MfyW%1f_?fR?Ckz)Biju2#4r4`fBbBX-FD}16eXR0qBracpszR8z_odttDtZ(C2LE1i&I64?sm{5lAAzo|x1Qz)kKnARUM` z#|n7VKoukSJbegb)0k1LIox3tmG0#-re4d=4A9Mvk5h@kE%^?r@%9ubn%H&Tx`#B*C75H$IE#xIPfpFB^(}r>R(0&)Ev&2ZK3nS4ym?-i&N>^XT5bqv0KL z=3YlEcvsbUfK-$}#i0Sj5*yi)CcB!MIz%qm0C?(Ctru!q3*E-Ick+k{S)_9;i7&wF>}( z>49T1%YwP`gy|S3w2oS-8!qbZQ1YNA38RB+BNM_a>io~%+ybr!*d#WTQ*|qP9J0Zk z4t*v*^-DwIkD~aSwWzD5^T#2B&P+y68$MhinRPlBq|`iTVc%EQUE{pR7#OxhQatVN zS|0g*l}+%*yOI-lz#tWwk3Hfq3ChB@zv9kBn4&5-@D@%pN}u)-s}B_wx1>3~WqZ|> zVG8HuS#2v~k+A5`1_WMuUX+68JPSp2zcEY}X8Ti?S`KkUzsRSuteSbQX{xTQKRH%< zfM0IVnuE@n!=2PWJ?wUTZFZzoCM>pl@v`VroCoVtWy{9X?AWdSdE|C9d@XlVSd?vVqHk&Gzgv8;t8{@`#V%EW)Vo zhh|pcIVD1U1NS*|pL3s}VUs`oMbby?AtmA)rr9~`Fms)`(e5()k;pXtWw67K0t2I> zN)etA!_3y=@cz6L^L^U*^Nf&eyLnhZw<9~;>3Y(zSa#z3noLbzVyjD$rQJCA81;5; zq5ms1_s`v7R>=#e>Id7RA2_HBY!4HuYfD1pw={r2&z6c*@#UwrShpqP32h-19S z45_`@YR#;7voUgODEK9B3DSg;%jTQjC2%p#FQ%i;_fO7g?<<0gdyDJj8e=XKimJ(8 z-cd_G>v%5#!_n{upU(oiU%Z+f0!unX${O(j+h$W^m2Li-au+aYU?KY+9mqz`zL*rm z1tHZc*m-uY`ih)ey)0?G`0L%VJfPweL4zq{4)~77gc(voku99vS;o|7LLhHoTJt%o z#j{+rXKkj~o+`2Y3%fSihZ3^(j62*0#Wy?3Z5X3PxLNO}pWRTt38co8f@F}(0F}qNcz0(THglLJu?^=2H?m5RHX;_a8dBDUS8J1Me!^U}<*L2B zkSjCxA*|_GVQT)c!?18cpeGq5F4UA}$Z~7qDi*F;dJS~^UGzfik>slR($O-=CDHlp zPI9_?EY`kPDj}E}Wxx8y1iD@%Jz&uqR?#1H$c{j~KhdH9aSQn%5BEtt=0!TKY>=6# z5;U#W$;wP=Pcs@Yx?#LIJTa4dz?eYNXe=3&myDHCLZCJU68u_3& z9uv?;9{x(UOXMCu7RtjIB)f(^FVQ|ZjM7gFBL-+KKu})=%PRRC(e96DYX@9V%KNEd z*Bw?fP(7sOz!{;oPbTD{OjEHvfi0cls}mHu*%xhp{Pr95{$C7ld_trusU;POGPGDH zS9M@k%5sq4uFBHkVZ>XechuRA*;>AvM_oD(<%DWeGnHBcX^F*^vg{mgwbc$bL}?P9 z;Td0iRCwZhPhcj7C}~={+}Z?OxfGXAuM$PhYMhQhQn55tT||g1hQ602{t2YEhzp$a zI4c#Ul$5St()aC#J^SGcpfb-m-fw<5V<4DVM43hkH~HgL1qvQvP3cs~Ew?E=+M5r< zXXVY2P4?m@ci`{SFG-s>eWEwHJ+E*^)hY%U!P(H#$SJ@9ysd=v4m=N8|X@Lqg+tr+w4Y0^2E;LsOcA%DzaX98==}OfP$9vrX ztwR*@K{fGnSbHQPH|}$Wy70JrPgT@Hg=XVaRTRpg&b!5957)J%$<~BiMD7{e-}jxz zTS=GjKnh+zw=n6LME#*}H4Fg*nw;RP&o_tM(V**zYIXgMb4~W_KkgXj&ZW{z0XeX-=*7x0q*5$fOQ$Qi*ZG4E_F4K8a)Dtirc!T9qqW-3gv!2Q7EILtpSy+K4D{*N${Cezd zQ+IsbMF)irS(PP>|=(VYq}k9%-|>ZtcKUpx(md_ zh3AEYkME(#$%cq#!Fm&qxYz587-4t+pj=+(^CR zM@tJOC({MQ0bZs)3pr%8Q{ZgrN~z1q%ROQg6d8-~w|X6SzYTgPnMFO0I-Pjz(j?BP z$Seb?Bps`qaLtkUolkMRFhwTAdF6D#{Aa%(qa3rH$pg+-QlRo_g;!z@qj$4+yE1^x z(BH6pPA60))lfY-dVN>9BgnI|jS^E_Yca$|l%2r54aYi@2L#UWdg*&T)jLqQ>Sl9O zU|gqcmKI8I@`AS77{AC%7AX2kS^C1o}Z#$hjJB>&+>$-%&4c39=0*$L9nYb!Q6@&J0naJhlLDHC-#n9uk{<*`LG6Q zhSMq24LLS_c~?grfsvd?W#3?Y%4|Y!Z2(y#XR6a7As@*CKbm-ksZ1W6g1p;Fl-TT} z_EV?2vuqW=orLO#LG?YXH2DT^+Nr$#W>@5D*!WdOiS+T-6~)s8;T726&HIswPL~q7 zE1Dq^DG3bbzml)6^>yCd;4BZDQHXH1tMb#cdoj&w*D+x- zn6Wcn;YJA2x+grvp;PRZrK2Olc6Ew-L$Iw1ujh*^mfNGl6AH#F+NVhA#>&|)zn;tK zsZ=jU-FTZ$ZyhT5Fv(9EqJy$qpBCM(hl{(d@YL+*8F$r%0zhD-1c=ea5mbZRk}=qz3hOofRe!_2>q7&tnx-6wWD6`waeXFhHIk(whzjAQA)RX1@yEJ52$WC55`;z%sB-Us7vqD!ppmJMJ-#M;|tF zeY5K-gqbx)Hs+}>7}pU$@d*n8b%riQ^^ZHHgA9n|d)DtEG_yJ~Sa@L8G-MxLU#Nvr ztO+modD!c_gBkhMb7Z+wX3(sH>gJi-En?M)iS)%N^yY_iOm|oMwdWf1jRx(7=s&50 zHFGs?*Wq;cl*A>aj?0fM|DyXj2iaC@I4$BKrjI`ZS;j&uF>*C>XUvH4!DR^ownbOr z?>z$p_~i)hD-F}~$N6p8MnXraU-_JMd-+g8DdG)EYTU2=xEO)pFyppkta;2#mQzuOR;`7}U^XL$%W+M_K3r9d4(R5_cD zkeO7@BtK8s4--l)tj1=2kYn9k5euumaA>{tXKVk{7%N49SQ?3#TXW$kQ23ZeY#lJV zG`xD+!xPVY9c&c>#Ko_{FjtAqw_f0<55&;TLEMIUOcEO%S{4Uc?k1qr{f@G1#Ocl+*0o8I|r?7*7u9Gm!S z*O{%{RF)g`mTiG?(8n>B5YZH$DstYcw~6eU^4_~Sx)2RqAPRmE}4cA2J zIQ}S#ObuS%3`mDfg#x3x6sbVse3a`(K`!_rC~~65G@u8)5JO!*LHyx^aHL)kd^+Mf zT2?gDEOmQ1O;>OwGTc^gsdgDXECeqpDaJcwp-2P1ZJ84Xhm?B$(-9o%gT(M3l}xWa1&6} zt^Q77lK9z)1Gw2ov~o828td0L?H;u8Rm57iUv<-{q*))nDLjg2TaSQredc1&JPy7z z!R&fe2I9~nJ|41gx0kRrOx7I^HZCnwp-ri9GU;QZXl9xzj3ylCRdu;hnWtn{WUo_2 z6$j>71)TMSmzV+ip3^~0GExIaTJdHT=I^JDHLg~Tj=1zABq&2SooC-@~MVf;@-z-o&RFn8k3717b+Rp;yx3rGqxSAzfzP4Ji zhK`E56u}8!jN1(=v?|)vdFH%&yy#{I6Z{tZxf5}j{{q}TtGhjEv?=S-NPeV5OF{I! zbDQHI(|<5*`V<%qR?ZzhyTBxK_4!t;;kNWmXEej1#+FP2d2sXF8Zw>ANE7p|RFUOo? zjgvh)>QHkx^~;m&MAB50nNRQxiFbD39CViz^zI}k!@`7rX1+@qeziErOmo%c3KCkj(R(6XAOL)$d9%J&h!Q# ziiJ+jp@~!2xgZCkotpo9Yef3|;UlL+v+RJaUe@Lw6vorD!f(A}q zoCu^U*L-6zJp$RjoH&F0UwL}7n&*ZHhw^GI?3bgfTYjvSV~ATbvcnpTxK3WZsmXiE zI1VXdYD)yFxo(oM?#ym+Ocf2q>H21?K+2Ntl&l2SHN--$df@YEWx>z&0UvrUX8;l3 z{_dCT98MX5whX9Ocn>EuX-cG~M=yu9JI6b^NW_KHU8(iP_<3^Ya9ShTO)E{SS)N;4 z1)k|8>ET|iZ2O~i{$iT5?w%&6WcgONks$hNc~3gM-4y-L{VPuo@XRFFnonHby^sI$ zsRImj8_P%c$4Klsn>|h#KnW;rbsK#f>lOC+h3%^1e||F=Afln#qYvCe>;m->tzXqN-@=l8YzMel3Z zSU9h2Oat#lME~znlml_V=$x`I9sGpWE>Nkw^JL-9HIJ WJz`YU+}H None: services = inputs["services"] for service in services: for entity in service["entity"]: - if entity["type"] == "checkboxGroup": + if ( + entity["type"] == "checkboxGroup" + or entity["type"] == "CheckboxTree" + ): row_field_names = [] for row in entity["options"]["rows"]: if row["field"] in row_field_names: diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json index 664c89d90..969b1eaa6 100644 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json @@ -845,6 +845,81 @@ "field": "input_two_checkbox", "help": "This is an example checkbox for the input two entity" }, + { + "type": "CheckboxTree", + "label": "Event Filters", + "field": "apiss", + "required": true, + "options": { + "groups": [ + { + "label": "Transactions", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "slow_request", + "transaction_stall" + ] + }, + { + "label": "others", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "custom_events", + "cluster_events", + "network_events" + ] + } + ], + "rows": [ + { + "field": "code_problems", + "checkbox": { + "label": "Code Problems", + "defaultValue": true + } + }, + { + "field": "slow_request", + "checkbox": { + "label": "Slow Request", + "defaultValue": true + } + }, + { + "field": "transaction_stall", + "checkbox": { + "label": "Transactions Stall", + "defaultValue": false + } + }, + { + "field": "custom_events", + "checkbox": { + "label": "Custom Events", + "defaultValue": true + } + }, + { + "field": "cluster_events", + "checkbox": { + "label": "Cluster Events" + } + }, + { + "field": "network_events", + "checkbox": { + "label": "Network Events" + } + } + ] + } + }, { "type": "radio", "label": "Example Radio", diff --git a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json index b6393c616..0fb5a4867 100644 --- a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json @@ -497,6 +497,81 @@ "label": "Interval", "help": "Time interval of the data input, in seconds.", "required": true + }, + { + "type": "CheckboxTree", + "label": "Event Filters", + "field": "apiss", + "required": true, + "options": { + "groups": [ + { + "label": "Transactions", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "slow_request", + "transaction_stall" + ] + }, + { + "label": "others", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": [ + "custom_events", + "cluster_events", + "network_events" + ] + } + ], + "rows": [ + { + "field": "code_problems", + "checkbox": { + "label": "Code Problems", + "defaultValue": true + } + }, + { + "field": "slow_request", + "checkbox": { + "label": "Slow Request", + "defaultValue": true + } + }, + { + "field": "transaction_stall", + "checkbox": { + "label": "Transactions Stall", + "defaultValue": false + } + }, + { + "field": "custom_events", + "checkbox": { + "label": "Custom Events", + "defaultValue": true + } + }, + { + "field": "cluster_events", + "checkbox": { + "label": "Cluster Events" + } + }, + { + "field": "network_events", + "checkbox": { + "label": "Network Events" + } + } + ] + } } ], "table": { diff --git a/tests/unit/test_global_config_validator.py b/tests/unit/test_global_config_validator.py index 43dc3b9f2..1e2490544 100644 --- a/tests/unit/test_global_config_validator.py +++ b/tests/unit/test_global_config_validator.py @@ -281,6 +281,24 @@ def test_autocompletefields_children_support_integer_values(): "Entity test_checkbox_group has duplicate field (collectTasksAndComments) in options.groups" ), ), + ( + "invalid_config_checkbox_tree_duplicate_field_in_options_rows.json", + ( + "Entity test_checkbox_tree has duplicate field (rowUnderGroup1) in options.rows" + ), + ), + ( + "invalid_config_checkbox_tree_undefined_field_used_in_groups.json", + ( + "Entity test_checkbox_tree uses field (undefinedRow) which is not defined in options.rows" + ), + ), + ( + "invalid_config_checkbox_tree_duplicate_field_in_options_groups.json", + ( + "Entity test_checkbox_tree has duplicate field (firstRowUnderGroup3) in options.groups" + ), + ), ( "invalid_config_group_has_duplicate_labels.json", ( diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json new file mode 100644 index 000000000..2bf9d2ee6 --- /dev/null +++ b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json @@ -0,0 +1,101 @@ +{ + "meta": { + "name": "Splunk_TA_aws", + "displayName": "Splunk Add-on for AWS", + "version": "7.1.0", + "restRoot": "restRoot", + "schemaVersion": "0.0.3" + }, + "pages": { + "configuration": { + "title": "", + "tabs": [ + { + "name": "a", + "title": "", + "entity": [] + } + ] + }, + "inputs": { + "title": "Inputs", + "table": { + "header": [ + { + "field": "name", + "label": "Input Name" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + } + ], + "actions": ["edit", "delete", "search", "clone"] + }, + "services": [ + { + "name": "example_input_four", + "title": "Title example", + "entity": [ + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "test_checkbox_tree", + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1", "firstRowUnderGroup3"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "firstRowUnderGroup3", + "checkbox": { + "label": "first row under group 3", + "defaultValue": true + } + }, + { + "field": "secondRowUnderGroup3", + "checkbox": { + "label": "second row under group 3" + } + } + ] + } + } + ] + } + ] + } + } +} diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json new file mode 100644 index 000000000..ac0eff99a --- /dev/null +++ b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json @@ -0,0 +1,108 @@ +{ + "meta": { + "name": "Splunk_TA_aws", + "displayName": "Splunk Add-on for AWS", + "version": "7.1.0", + "restRoot": "restRoot", + "schemaVersion": "0.0.3" + }, + "pages": { + "configuration": { + "title": "", + "tabs": [ + { + "name": "a", + "title": "", + "entity": [] + } + ] + }, + "inputs": { + "title": "Inputs", + "table": { + "header": [ + { + "field": "name", + "label": "Input Name" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + } + ], + "actions": ["edit", "delete", "search", "clone"] + }, + "services": [ + { + "name": "example_input_four", + "title": "Title example", + "entity": [ + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "test_checkbox_tree", + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "firstRowUnderGroup3", + "checkbox": { + "label": "first row under group 3", + "defaultValue": true + } + }, + { + "field": "secondRowUnderGroup3", + "checkbox": { + "label": "second row under group 3" + } + } + ] + } + } + ] + } + ] + } + } +} diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json b/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json new file mode 100644 index 000000000..56c4ffba1 --- /dev/null +++ b/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json @@ -0,0 +1,101 @@ +{ + "meta": { + "name": "Splunk_TA_aws", + "displayName": "Splunk Add-on for AWS", + "version": "7.1.0", + "restRoot": "restRoot", + "schemaVersion": "0.0.3" + }, + "pages": { + "configuration": { + "title": "", + "tabs": [ + { + "name": "a", + "title": "", + "entity": [] + } + ] + }, + "inputs": { + "title": "Inputs", + "table": { + "header": [ + { + "field": "name", + "label": "Input Name" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + } + ], + "actions": ["edit", "delete", "search", "clone"] + }, + "services": [ + { + "name": "example_input_four", + "title": "Title example", + "entity": [ + { + "type": "CheckboxTree", + "label": "CheckboxTreeTitle", + "field": "test_checkbox_tree", + "options": { + "groups": [ + { + "label": "Group 1", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["rowUnderGroup1", "undefinedRow"] + }, + { + "label": "Group 3", + "options": { + "isExpandable": true, + "expand": true + }, + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] + } + ], + "rows": [ + { + "field": "rowWithoutGroup", + "checkbox": { + "label": "Row without group", + "defaultValue": true + } + }, + { + "field": "rowUnderGroup1", + "checkbox": { + "label": "Row under Group 1", + "defaultValue": true + } + }, + { + "field": "firstRowUnderGroup3", + "checkbox": { + "label": "first row under group 3", + "defaultValue": true + } + }, + { + "field": "secondRowUnderGroup3", + "checkbox": { + "label": "second row under group 3" + } + } + ] + } + } + ] + } + ] + } + } +} diff --git a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx index 3f05e5a36..61c2053be 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx @@ -10,7 +10,7 @@ const handleChange = jest.fn(); const defaultCheckboxProps: CheckboxTreeProps = { mode: MODE_CREATE, field: 'apis', - value: 'rowUnderGroup1,requiredField', + value: 'rowUnderGroup1,firstRowUnderGroup3', label: 'CheckboxTree', controlOptions: { groups: [ @@ -28,7 +28,7 @@ const defaultCheckboxProps: CheckboxTreeProps = { isExpandable: true, expand: true, }, - fields: ['requiredField', '160validation'], + fields: ['firstRowUnderGroup3', 'secondRowUnderGroup3'], }, ], rows: [ @@ -47,16 +47,16 @@ const defaultCheckboxProps: CheckboxTreeProps = { }, }, { - field: 'requiredField', + field: 'firstRowUnderGroup3', checkbox: { - label: 'Required field', + label: 'first row under group 3', defaultValue: false, }, }, { - field: '160validation', + field: 'secondRowUnderGroup3', checkbox: { - label: 'from 1 to 60 validation', + label: 'second row under group 3', }, }, ], @@ -84,8 +84,8 @@ describe('CheckboxTree Component', () => { // Verify rows expect(screen.getByLabelText('Row without group')).toBeInTheDocument(); expect(screen.getByLabelText('Row under Group 1')).toBeInTheDocument(); - expect(screen.getByLabelText('Required field')).toBeInTheDocument(); - expect(screen.getByLabelText('from 1 to 60 validation')).toBeInTheDocument(); + expect(screen.getByLabelText('first row under group 3')).toBeInTheDocument(); + expect(screen.getByLabelText('second row under group 3')).toBeInTheDocument(); }); it('handles "Select All" and "Clear All" functionality', async () => { diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json index f969e5add..389647d05 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json @@ -59,7 +59,7 @@ "isExpandable": true, "expand": true }, - "fields": ["requiredField", "160validation"] + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] } ], "rows": [ @@ -78,16 +78,16 @@ } }, { - "field": "requiredField", + "field": "firstRowUnderGroup3", "checkbox": { - "label": "Required field", + "label": "first row under group 3", "defaultValue": true } }, { - "field": "160validation", + "field": "secondRowUnderGroup3", "checkbox": { - "label": "from 1 to 60 validation" + "label": "second row under group 3" } } ] diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json index 514a75400..3878d0bdb 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json @@ -60,7 +60,7 @@ "isExpandable": true, "expand": true }, - "fields": ["requiredField", "160validation"] + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] } ], "rows": [ @@ -79,16 +79,16 @@ } }, { - "field": "requiredField", + "field": "firstRowUnderGroup3", "checkbox": { - "label": "Required field", + "label": "first row under group 3", "defaultValue": false } }, { - "field": "160validation", + "field": "secondRowUnderGroup3", "checkbox": { - "label": "from 1 to 60 validation" + "label": "second row under group 3" } } ] diff --git a/ui/src/components/CheckboxTree/utils.test.tsx b/ui/src/components/CheckboxTree/utils.test.tsx index e53e1aba5..1039a9ec9 100644 --- a/ui/src/components/CheckboxTree/utils.test.tsx +++ b/ui/src/components/CheckboxTree/utils.test.tsx @@ -71,14 +71,4 @@ describe('packValue', () => { const result = packValue(map); expect(result).toBe(''); }); - - test('should handle maps with mixed keys and ignore invalid ones', () => { - const map: ValueByField = new Map([ - ['field1', { checkbox: true }], - ['field2', { checkbox: true }], - ['field3', { invalid: true } as any], - ]); - const result = packValue(map); - expect(result).toBe('field1,field2'); - }); }); From 6bc85b88d64fc310671d1c248cc3dd4f802f2f37 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Mon, 9 Dec 2024 05:53:23 +0000 Subject: [PATCH 08/23] update screenshots --- .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-required-view-chromium.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index 66ea6e90c..70bdb2d5d 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48c9586c25fc326bd82c3546aab1da8bf7af77a62ed1d5d1905861c43b5eaf21 -size 35864 +oid sha256:19e3a3d6acd8df1d58753b7ca6f59ecaf06a742a3e9120c29d8b6efb9b231ba7 +size 37520 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png index 07ff1fc5b..c03a313f4 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4686285bb899c5d92aa1ddafce2e50349d0535b4c2bb5bc515b6dc827c54d821 -size 39334 +oid sha256:1f20662b9e6a5a18d0b051eb9aa520109e4a5e404193b975e4ee544707b8b902 +size 40824 From b274197f431a1af7b078498b3a1b8ad1b84132f4 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Mon, 9 Dec 2024 12:11:32 +0530 Subject: [PATCH 09/23] fix: added dependency for the variable in input.conf --- .../Splunk_TA_UCCExample/README/inputs.conf.spec | 1 + .../bin/splunk_ta_uccexample_rh_three_custom.py | 4 ++-- .../package/bin/splunk_ta_uccexample_rh_three_custom.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec index a5301b425..7c9359aea 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec @@ -34,6 +34,7 @@ start_date = The date and time, in "YYYY-MM-DDThh:mm:ss.000z" format, after whic use_existing_checkpoint = Data input already exists. Select `No` if you want to reset the data collection. Default: yes [example_input_three://] +apis = interval = Time interval of the data input, in seconds. [example_input_four://] diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py index 3d73f4f2f..e3031886a 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py @@ -28,7 +28,7 @@ 'disabled', required=False, validator=None - ) + ), field.RestField( 'apis', @@ -36,7 +36,7 @@ encrypted=False, default=None, validator=None - ), + ) ] model = RestModel(fields, name=None) diff --git a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py index ea9d65ca5..e3031886a 100644 --- a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py @@ -36,7 +36,7 @@ encrypted=False, default=None, validator=None - ), + ) ] model = RestModel(fields, name=None) From 53a77535c02236ab3632bdf14eb39f7f122f2cde Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Tue, 10 Dec 2024 18:19:21 +0530 Subject: [PATCH 10/23] refactor: update the logic for search to collapse/expand on input --- .../CheckboxTree/CheckboxSubTree.tsx | 15 ++-- .../components/CheckboxTree/CheckboxTree.tsx | 73 ++++++++++++------- .../CheckboxTree/StyledComponent.tsx | 1 + .../stories/CheckboxTreeMocks.json | 35 +++++++-- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx index de02208f9..c6bee925e 100644 --- a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; import { getCheckedCheckboxesCount } from './CheckboxTree.utils'; import { @@ -26,7 +26,7 @@ const CheckboxSubTree: React.FC = ({ disabled, handleParentCheckboxTree, }) => { - const [isExpanded, setIsExpanded] = useState(true); + const [isExpanded, setIsExpanded] = useState(group.options?.expand); const isParentChecked = useMemo( () => group.rows.every((row) => values.get(row.field)?.checkbox), @@ -34,7 +34,7 @@ const CheckboxSubTree: React.FC = ({ ); const isIndeterminate = useMemo( - () => group.rows.some((row) => values.get(row.field)?.checkbox) && !isParentChecked, + () => group.rows.some((row) => !isParentChecked && values.get(row.field)?.checkbox), [group.rows, values, isParentChecked] ); @@ -43,11 +43,16 @@ const CheckboxSubTree: React.FC = ({ [group, values] ); + useEffect(() => { + setIsExpanded(group.options?.expand); + }, [group.options?.expand, group.rows]); + const toggleCollapse = () => setIsExpanded((prev) => !prev); const ParentCheckbox = ( { @@ -59,7 +64,7 @@ const CheckboxSubTree: React.FC = ({ onChange={() => handleParentCheckboxTree(group.label, !isParentChecked)} disabled={disabled} /> - {group.label} + ); @@ -79,7 +84,7 @@ const CheckboxSubTree: React.FC = ({ const description = ( - {checkedCheckboxesCount} of {group.fields.length} + {checkedCheckboxesCount} of {group.rows.length} ); diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx index f26730d49..0e385f40d 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -27,16 +27,19 @@ function CheckboxTree(props: CheckboxTreeProps) { const [values, setValues] = useState(initialValues); const [searchForCheckBoxValue, setSearchForCheckBoxValue] = useState(''); - if (required) { - checkValidationForRequired(props.field, props.label, controlOptions.rows); - } + + useEffect(() => { + if (required) { + checkValidationForRequired(field, props.label, controlOptions.rows); + } + }, [required, field, controlOptions.rows, props.label]); // Propagate default values on mount if applicable useEffect(() => { if (shouldUseDefaultValue) { handleChange(field, packValue(initialValues), 'CheckboxTree'); } - }, [field, handleChange, shouldUseDefaultValue, initialValues]); + }, [shouldUseDefaultValue, field, initialValues, handleChange]); const handleRowChange = useCallback( (newValue: { field: string; checkbox: boolean; text?: string }) => { @@ -72,27 +75,6 @@ function CheckboxTree(props: CheckboxTreeProps) { [controlOptions, field, handleChange] ); - const handleCheckboxToggleAll = useCallback( - (newCheckboxValue: boolean) => { - setValues((prevValues) => { - const updatedValues = new Map(prevValues); - controlOptions.rows.forEach((row) => { - updatedValues.set(row.field, { checkbox: newCheckboxValue }); - }); - handleChange(field, packValue(updatedValues), 'CheckboxTree'); - return updatedValues; - }); - }, - [controlOptions.rows, field, handleChange] - ); - - const handleSearchChange = useCallback( - (e: React.SyntheticEvent, { value: searchValue }: SearchChangeData) => { - setSearchForCheckBoxValue(searchValue); - }, - [] - ); - const filterRows = useCallback(() => { const searchValueLower = searchForCheckBoxValue.toLowerCase(); @@ -107,7 +89,17 @@ function CheckboxTree(props: CheckboxTreeProps) { ); return groupMatches || filteredRows.length > 0 - ? { ...row, rows: filteredRows } + ? { + ...row, + rows: filteredRows, + options: { + ...row.options, + expand: + searchValueLower !== '' && row.options?.isExpandable + ? true + : row.options?.expand, + }, + } : []; } @@ -119,6 +111,35 @@ function CheckboxTree(props: CheckboxTreeProps) { const filteredRows = filterRows(); + const handleCheckboxToggleAll = useCallback( + (newCheckboxValue: boolean) => { + setValues((prevValues) => { + const updatedValues = new Map(prevValues); + + filteredRows.forEach((row) => { + if (row && isGroupWithRows(row)) { + row.rows.forEach((childRow) => { + updatedValues.set(childRow.field, { checkbox: newCheckboxValue }); + }); + } else if (row) { + updatedValues.set(row.field, { checkbox: newCheckboxValue }); + } + }); + + handleChange(field, packValue(updatedValues), 'CheckboxTree'); + return updatedValues; + }); + }, + [filteredRows, field, handleChange] + ); + + const handleSearchChange = useCallback( + (e: React.SyntheticEvent, { value: searchValue }: SearchChangeData) => { + setSearchForCheckBoxValue(searchValue); + }, + [] + ); + return ( <> Date: Tue, 10 Dec 2024 12:53:02 +0000 Subject: [PATCH 11/23] update screenshots --- .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-mixed-with-groups-chromium.png | 4 ++-- .../__images__/CheckboxTree-required-view-chromium.png | 4 ++-- .../stories/__images__/CheckboxTree-search-chromium.png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index 70bdb2d5d..8d770411c 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19e3a3d6acd8df1d58753b7ca6f59ecaf06a742a3e9120c29d8b6efb9b231ba7 -size 37520 +oid sha256:0659bd14bf6a2ce8fc71db198d1bcec7e1e6503ab7e9fa41c98b2100d1db1ac5 +size 48856 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png index 2638b047e..236f7cf33 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f4bef9b40b94d68d64ccdbd5ce4e589fd86cef32a01fa5f0be54264cac11438 -size 25038 +oid sha256:bb647a3678b7e691fb1e6fd15afa37dd097d737463fc517ad5dd043150c6c8b2 +size 24954 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png index c03a313f4..3697e3d75 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f20662b9e6a5a18d0b051eb9aa520109e4a5e404193b975e4ee544707b8b902 -size 40824 +oid sha256:b5bc16e1b9a0c60ec7c130f1d008efd99f851911c2c417754979ad1f341fccfe +size 40742 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png index 716d844b6..d82588167 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3e85bd398419f6c991b5aa6bcf745557f3b2f88b7c812fa658812e9dc2bbe81 -size 11471 +oid sha256:0f88be5edc2d3410edf57152eea0d8e49ba044c644bf5fc7b77da84001ba4135 +size 11364 From 1685dc785fd6290577216c73731810a3592b58af Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Thu, 12 Dec 2024 11:12:42 +0530 Subject: [PATCH 12/23] revert(search): remove the search functionality from checkboxtree --- .../components/CheckboxTree/CheckboxTree.tsx | 71 ++----------------- ui/src/components/CheckboxTree/types.ts | 4 -- 2 files changed, 6 insertions(+), 69 deletions(-) diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx index 0e385f40d..5e9fea900 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import ColumnLayout from '@splunk/react-ui/ColumnLayout'; import Button from '@splunk/react-ui/Button'; -import Search from '@splunk/react-ui/Search'; import { StyledColumnLayout } from './StyledComponent'; import { getDefaultValues, @@ -12,7 +11,7 @@ import { import CheckboxSubTree from './CheckboxSubTree'; import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; import { MODE_CREATE } from '../../constants/modes'; -import { CheckboxTreeProps, SearchChangeData, ValueByField } from './types'; +import { CheckboxTreeProps, ValueByField } from './types'; import { packValue, parseValue } from './utils'; import { checkValidationForRequired } from './validation'; @@ -26,7 +25,6 @@ function CheckboxTree(props: CheckboxTreeProps) { : parseValue(props.value); const [values, setValues] = useState(initialValues); - const [searchForCheckBoxValue, setSearchForCheckBoxValue] = useState(''); useEffect(() => { if (required) { @@ -39,7 +37,7 @@ function CheckboxTree(props: CheckboxTreeProps) { if (shouldUseDefaultValue) { handleChange(field, packValue(initialValues), 'CheckboxTree'); } - }, [shouldUseDefaultValue, field, initialValues, handleChange]); + }, [field, handleChange, shouldUseDefaultValue, initialValues]); const handleRowChange = useCallback( (newValue: { field: string; checkbox: boolean; text?: string }) => { @@ -75,81 +73,24 @@ function CheckboxTree(props: CheckboxTreeProps) { [controlOptions, field, handleChange] ); - const filterRows = useCallback(() => { - const searchValueLower = searchForCheckBoxValue.toLowerCase(); - - return flattenedRowsWithGroups - .flatMap((row) => { - if (isGroupWithRows(row)) { - const groupMatches = row.label.toLowerCase().includes(searchValueLower); - const filteredRows = groupMatches - ? row.rows - : row.rows.filter((childRow) => - childRow.checkbox?.label?.toLowerCase().includes(searchValueLower) - ); - - return groupMatches || filteredRows.length > 0 - ? { - ...row, - rows: filteredRows, - options: { - ...row.options, - expand: - searchValueLower !== '' && row.options?.isExpandable - ? true - : row.options?.expand, - }, - } - : []; - } - - const rowMatches = row.checkbox?.label?.toLowerCase().includes(searchValueLower); - return rowMatches ? row : null; - }) - .filter(Boolean); - }, [flattenedRowsWithGroups, searchForCheckBoxValue]); - - const filteredRows = filterRows(); - const handleCheckboxToggleAll = useCallback( (newCheckboxValue: boolean) => { setValues((prevValues) => { const updatedValues = new Map(prevValues); - - filteredRows.forEach((row) => { - if (row && isGroupWithRows(row)) { - row.rows.forEach((childRow) => { - updatedValues.set(childRow.field, { checkbox: newCheckboxValue }); - }); - } else if (row) { - updatedValues.set(row.field, { checkbox: newCheckboxValue }); - } + controlOptions.rows.forEach((row) => { + updatedValues.set(row.field, { checkbox: newCheckboxValue }); }); - handleChange(field, packValue(updatedValues), 'CheckboxTree'); return updatedValues; }); }, - [filteredRows, field, handleChange] - ); - - const handleSearchChange = useCallback( - (e: React.SyntheticEvent, { value: searchValue }: SearchChangeData) => { - setSearchForCheckBoxValue(searchValue); - }, - [] + [controlOptions.rows, field, handleChange] ); return ( <> - - {filteredRows.map((row) => + {flattenedRowsWithGroups.map((row) => row && isGroupWithRows(row) ? ( void; disabled?: boolean; } - -export type SearchChangeData = { - value: string; -}; From 08ba88186b30dc17ef76099c85f04a59918a163f Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Fri, 13 Dec 2024 15:20:14 +0530 Subject: [PATCH 13/23] refactor: change styling and storybook --- docs/entity/components.md | 4 +- .../global_config_validator.py | 2 +- .../schema/schema.json | 4 +- .../README/inputs.conf.spec | 2 +- .../splunk_ta_uccexample_rh_three_custom.py | 2 +- .../globalConfig.json | 4 +- .../splunk_ta_uccexample_rh_three_custom.py | 2 +- .../globalConfig.json | 4 +- .../globalConfig.json | 4 +- ...ree_duplicate_field_in_options_groups.json | 2 +- ..._tree_duplicate_field_in_options_rows.json | 2 +- ...x_tree_undefined_field_used_in_groups.json | 2 +- .../CheckboxTree/CheckboxSubTree.tsx | 21 +++--- .../CheckboxTree/CheckboxTree.test.tsx | 16 +++-- .../components/CheckboxTree/CheckboxTree.tsx | 45 +++++++------ .../CheckboxTree/CheckboxTree.utils.ts | 16 +++-- .../CheckboxTree/StyledComponent.tsx | 61 +++++++++++++++++ .../stories/CheckboxTree.stories.tsx | 67 +++++++++++-------- .../stories/CheckboxTreeMocks.json | 2 +- .../stories/CheckboxTreeRequiredMocks.json | 2 +- ui/src/components/CheckboxTree/types.ts | 2 +- ui/src/components/CheckboxTree/validation.ts | 26 ------- ui/src/constants/ControlTypeMap.ts | 2 +- ui/src/types/globalConfig/entities.ts | 2 +- 24 files changed, 172 insertions(+), 124 deletions(-) delete mode 100644 ui/src/components/CheckboxTree/validation.ts diff --git a/docs/entity/components.md b/docs/entity/components.md index 1f0cc3a1c..6669f6937 100644 --- a/docs/entity/components.md +++ b/docs/entity/components.md @@ -321,13 +321,13 @@ This is how it looks in the UI: The component maps and unmaps values into a single field in the format `fieldName1/fieldValue1,fieldName2/fieldValue2`, but only for checked rows. For the given example, it emits the following value: `rowUnderGroup1/1200,requiredField/10`. -## `CheckboxTree` +## `checkboxTree` See the following example usage: ```json { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "api3", "options": { diff --git a/splunk_add_on_ucc_framework/global_config_validator.py b/splunk_add_on_ucc_framework/global_config_validator.py index eeb36f2a3..36d88e5ad 100644 --- a/splunk_add_on_ucc_framework/global_config_validator.py +++ b/splunk_add_on_ucc_framework/global_config_validator.py @@ -476,7 +476,7 @@ def _validate_checkbox_group(self) -> None: for entity in service["entity"]: if ( entity["type"] == "checkboxGroup" - or entity["type"] == "CheckboxTree" + or entity["type"] == "checkboxTree" ): row_field_names = [] for row in entity["options"]["rows"]: diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index 0c6aebbcd..559e68e4a 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -1461,9 +1461,9 @@ "description": "Text displayed next to entity field" }, "type": { - "const": "CheckboxTree", + "const": "checkboxTree", "type": "string", - "description": "Exactly: CheckboxTree" + "description": "Exactly: checkboxTree" }, "options": { "type": "object", diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec index 7c9359aea..079106b58 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec @@ -34,7 +34,7 @@ start_date = The date and time, in "YYYY-MM-DDThh:mm:ss.000z" format, after whic use_existing_checkpoint = Data input already exists. Select `No` if you want to reset the data collection. Default: yes [example_input_three://] -apis = +checkbox_field = interval = Time interval of the data input, in seconds. [example_input_four://] diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py index e3031886a..ee9539880 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/splunk_ta_uccexample_rh_three_custom.py @@ -31,7 +31,7 @@ ), field.RestField( - 'apis', + 'checkbox_field', required=False, encrypted=False, default=None, diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index 6da1d6f3a..335470e2c 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1573,9 +1573,9 @@ "required": true }, { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "Event Filters", - "field": "apis", + "field": "checkbox_field", "required": true, "options": { "groups": [ diff --git a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py index e3031886a..ee9539880 100644 --- a/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py +++ b/tests/testdata/test_addons/package_global_config_everything/package/bin/splunk_ta_uccexample_rh_three_custom.py @@ -31,7 +31,7 @@ ), field.RestField( - 'apis', + 'checkbox_field', required=False, encrypted=False, default=None, diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json index 969b1eaa6..8d9e4010a 100644 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json @@ -846,9 +846,9 @@ "help": "This is an example checkbox for the input two entity" }, { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "Event Filters", - "field": "apiss", + "field": "checkboxtree_field", "required": true, "options": { "groups": [ diff --git a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json index 0fb5a4867..f358fd3f1 100644 --- a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json @@ -499,9 +499,9 @@ "required": true }, { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "Event Filters", - "field": "apiss", + "field": "checkboxtree_field", "required": true, "options": { "groups": [ diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json index 2bf9d2ee6..8470aa239 100644 --- a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json +++ b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_groups.json @@ -40,7 +40,7 @@ "title": "Title example", "entity": [ { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "test_checkbox_tree", "options": { diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json index ac0eff99a..9ff667ac9 100644 --- a/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json +++ b/tests/unit/testdata/invalid_config_checkbox_tree_duplicate_field_in_options_rows.json @@ -40,7 +40,7 @@ "title": "Title example", "entity": [ { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "test_checkbox_tree", "options": { diff --git a/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json b/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json index 56c4ffba1..ea77c8653 100644 --- a/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json +++ b/tests/unit/testdata/invalid_config_checkbox_tree_undefined_field_used_in_groups.json @@ -40,7 +40,7 @@ "title": "Title example", "entity": [ { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "test_checkbox_tree", "options": { diff --git a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx index c6bee925e..4895874e8 100644 --- a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx @@ -4,6 +4,7 @@ import { getCheckedCheckboxesCount } from './CheckboxTree.utils'; import { CheckboxContainer, CheckboxWrapper, + CustomCheckbox, Description, GroupLabel, RowContainer, @@ -16,7 +17,7 @@ interface CheckboxSubTreeProps { values: ValueByField; handleRowChange: (newValue: { field: string; checkbox: boolean; text?: string }) => void; disabled?: boolean; - handleParentCheckboxTree: (groupLabel: string, newCheckboxValue: boolean) => void; + handleParentCheckboxForGroup: (groupLabel: string, newCheckboxValue: boolean) => void; } const CheckboxSubTree: React.FC = ({ @@ -24,7 +25,7 @@ const CheckboxSubTree: React.FC = ({ values, handleRowChange, disabled, - handleParentCheckboxTree, + handleParentCheckboxForGroup, }) => { const [isExpanded, setIsExpanded] = useState(group.options?.expand); @@ -34,7 +35,7 @@ const CheckboxSubTree: React.FC = ({ ); const isIndeterminate = useMemo( - () => group.rows.some((row) => !isParentChecked && values.get(row.field)?.checkbox), + () => !isParentChecked && group.rows.some((row) => values.get(row.field)?.checkbox), [group.rows, values, isParentChecked] ); @@ -51,20 +52,14 @@ const CheckboxSubTree: React.FC = ({ const ParentCheckbox = ( - { - const inputElement = el as HTMLInputElement | null; - if (inputElement) { - inputElement.indeterminate = isIndeterminate; - } - }} - onChange={() => handleParentCheckboxTree(group.label, !isParentChecked)} + data-indeterminate={isIndeterminate} + onChange={() => handleParentCheckboxForGroup(group.label, !isParentChecked)} disabled={disabled} /> - + {group.label} ); diff --git a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx index 61c2053be..e8aa8e16e 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.test.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.test.tsx @@ -9,9 +9,9 @@ const handleChange = jest.fn(); const defaultCheckboxProps: CheckboxTreeProps = { mode: MODE_CREATE, - field: 'apis', + field: 'checkbox_field', value: 'rowUnderGroup1,firstRowUnderGroup3', - label: 'CheckboxTree', + label: 'checkboxTree', controlOptions: { groups: [ { @@ -69,11 +69,7 @@ const renderCheckboxTree = (additionalProps?: Partial) => { render(); }; -describe('CheckboxTree Component', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - +describe('checkboxTree Component', () => { it('renders all rows and groups correctly', () => { renderCheckboxTree(); @@ -100,6 +96,11 @@ describe('CheckboxTree Component', () => { allCheckboxes.forEach((checkbox) => expect(checkbox).toBeChecked()); expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith( + 'checkbox_field', + 'rowUnderGroup1,firstRowUnderGroup3,rowWithoutGroup,secondRowUnderGroup3', + 'checkboxTree' + ); // "Clear All" const clearAllButton = screen.getByText('Clear All'); @@ -107,5 +108,6 @@ describe('CheckboxTree Component', () => { allCheckboxes.forEach((checkbox) => expect(checkbox).not.toBeChecked()); expect(handleChange).toHaveBeenCalledTimes(2); + expect(handleChange).toHaveBeenLastCalledWith('checkbox_field', '', 'checkboxTree'); }); }); diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx index 5e9fea900..826b045db 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useState, useCallback, useMemo } from 'react'; import ColumnLayout from '@splunk/react-ui/ColumnLayout'; import Button from '@splunk/react-ui/Button'; import { StyledColumnLayout } from './StyledComponent'; @@ -13,29 +13,32 @@ import CheckboxRowWrapper from './CheckboxTreeRowWrapper'; import { MODE_CREATE } from '../../constants/modes'; import { CheckboxTreeProps, ValueByField } from './types'; import { packValue, parseValue } from './utils'; -import { checkValidationForRequired } from './validation'; function CheckboxTree(props: CheckboxTreeProps) { - const { field, handleChange, controlOptions, disabled, required } = props; - const flattenedRowsWithGroups = getFlattenRowsWithGroups(controlOptions); - const shouldUseDefaultValue = - props.mode === MODE_CREATE && (props.value === null || props.value === undefined); - const initialValues = shouldUseDefaultValue - ? getDefaultValues(controlOptions.rows) - : parseValue(props.value); + const { field, handleChange, controlOptions, disabled } = props; - const [values, setValues] = useState(initialValues); + const flattenedRowsWithGroups = useMemo( + () => getFlattenRowsWithGroups(controlOptions), + [controlOptions] + ); - useEffect(() => { - if (required) { - checkValidationForRequired(field, props.label, controlOptions.rows); - } - }, [required, field, controlOptions.rows, props.label]); + const shouldUseDefaultValue = useMemo( + () => props.mode === MODE_CREATE && (props.value === null || props.value === undefined), + [props.mode, props.value] + ); + + const initialValues = useMemo( + () => + shouldUseDefaultValue ? getDefaultValues(controlOptions.rows) : parseValue(props.value), + [shouldUseDefaultValue, controlOptions.rows, props.value] + ); + + const [values, setValues] = useState(initialValues); // Propagate default values on mount if applicable useEffect(() => { if (shouldUseDefaultValue) { - handleChange(field, packValue(initialValues), 'CheckboxTree'); + handleChange(field, packValue(initialValues), 'checkboxTree'); } }, [field, handleChange, shouldUseDefaultValue, initialValues]); @@ -43,14 +46,14 @@ function CheckboxTree(props: CheckboxTreeProps) { (newValue: { field: string; checkbox: boolean; text?: string }) => { setValues((prevValues: ValueByField) => { const updatedValues = getNewCheckboxValues(prevValues, newValue); - handleChange(field, packValue(updatedValues), 'CheckboxTree'); + handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, [field, handleChange] ); - const handleParentCheckboxTree = useCallback( + const handleParentCheckboxForGroup = useCallback( (groupLabel: string, newCheckboxValue: boolean) => { if (!controlOptions?.groups) { return; @@ -66,7 +69,7 @@ function CheckboxTree(props: CheckboxTreeProps) { group.fields.forEach((item) => { updatedValues.set(item, { checkbox: newCheckboxValue }); }); - handleChange(field, packValue(updatedValues), 'CheckboxTree'); + handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, @@ -80,7 +83,7 @@ function CheckboxTree(props: CheckboxTreeProps) { controlOptions.rows.forEach((row) => { updatedValues.set(row.field, { checkbox: newCheckboxValue }); }); - handleChange(field, packValue(updatedValues), 'CheckboxTree'); + handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, @@ -98,7 +101,7 @@ function CheckboxTree(props: CheckboxTreeProps) { values={values} handleRowChange={handleRowChange} disabled={disabled} - handleParentCheckboxTree={handleParentCheckboxTree} + handleParentCheckboxForGroup={handleParentCheckboxForGroup} /> ) : ( diff --git a/ui/src/components/CheckboxTree/CheckboxTree.utils.ts b/ui/src/components/CheckboxTree/CheckboxTree.utils.ts index f9d066266..aaa42b906 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.utils.ts +++ b/ui/src/components/CheckboxTree/CheckboxTree.utils.ts @@ -59,14 +59,16 @@ export function getDefaultValues(rows: Row[]): ValueByField { const resultMap = new Map(); rows.forEach((row) => { - if (!isGroupWithRows(row)) { - const checkboxDefaultValue = row.checkbox?.defaultValue; - if (typeof checkboxDefaultValue === 'boolean') { - resultMap.set(row.field, { - checkbox: checkboxDefaultValue, - }); - } + if (isGroupWithRows(row)) { + return; + } + const checkboxDefaultValue = row.checkbox?.defaultValue; + if (typeof checkboxDefaultValue !== 'boolean') { + return; } + resultMap.set(row.field, { + checkbox: checkboxDefaultValue, + }); }); return resultMap; diff --git a/ui/src/components/CheckboxTree/StyledComponent.tsx b/ui/src/components/CheckboxTree/StyledComponent.tsx index 95aa1baaf..9eafb9cac 100644 --- a/ui/src/components/CheckboxTree/StyledComponent.tsx +++ b/ui/src/components/CheckboxTree/StyledComponent.tsx @@ -27,6 +27,8 @@ export const StyledCollapsiblePanel = styled(CollapsiblePanel)` font-size: 14px; margin-bottom: ${variables.spacingXSmall}; background-color: ${variables.neutral300}; + display: flex; + align-items: center; } `; @@ -52,6 +54,8 @@ export const Description = styled.span` font-size: 12px; display: flex; justify-content: end; + min-width: 35px; + align-items: center; `; export const CheckboxWrapper = styled.div` @@ -74,3 +78,60 @@ export const StyledRow = styled.div` justify-content: space-between; padding-top: 2px; `; + +export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` + appearance: none; + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + border: 2px solid #6b7280; + border-radius: 4px; + background-color: #ffffff; + cursor: pointer; + display: inline-block; + margin-right: 8px; + position: relative; + + &:checked { + background-color: #ffffff; + border: 2px solid #6b7280; + } + + &:checked::after { + content: ''; + position: absolute; + width: 12px; + height: 12px; + background-color: #6b7280; + clip-path: polygon( + 35.75% 85.22%, + 100% 8.1%, + 90.27% 0.02%, + 34.25% 67.35%, + 5% 45%, + 0% 55.84% + ); + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + + &[data-indeterminate='true']::after { + content: ''; + position: absolute; + width: 10px; + height: 2px; + background-color: #6b7280; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 1px; + } + + &:disabled { + border-color: #d1d5db; + background-color: #f9fafb; + cursor: not-allowed; + } +`; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx index c03df950a..3bdd8896a 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx +++ b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { fn, userEvent, within } from '@storybook/test'; +import { fn } from '@storybook/test'; import CheckboxTree from '../CheckboxTree'; import { MODE_CREATE, MODE_EDIT } from '../../../constants/modes'; @@ -153,25 +153,46 @@ export const MixedWithGroups: Story = { }, }; -export const CreateMode: Story = { +export const MultilineWithGroups: Story = { args: { ...Base.args, - value: undefined, - mode: MODE_CREATE, + value: 'collect_collaboration', controlOptions: { + groups: [ + { + label: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry dummy', + fields: ['lorem_ipsum1'], + options: { isExpandable: true }, + }, + { + label: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry', + fields: ['lorem_ipsum', 'lorem_ipsum2'], + options: { isExpandable: false }, + }, + ], rows: [ { - field: 'field1', + field: 'lorem_ipsum1', checkbox: { - label: 'checkbox list with default value true', - defaultValue: true, + label: 'Lorem ipsum dummy', }, }, { - field: 'field2', + field: 'lorem_ipsum2', checkbox: { - label: 'checkbox list with default value false', - defaultValue: false, + label: 'Lorem ipsum dummy text', + }, + }, + { + field: 'lorem_ipsum', + checkbox: { + label: 'Lorem ipsum', + }, + }, + { + field: 'collect_folder_metadata', + checkbox: { + label: 'Collect folder metadata', }, }, ], @@ -179,38 +200,28 @@ export const CreateMode: Story = { }, }; -export const Search: Story = { +export const CreateMode: Story = { args: { ...Base.args, value: undefined, + mode: MODE_CREATE, controlOptions: { - groups: [ - { - label: 'Group 1', - fields: ['collect_collaboration', 'collect_file'], - options: { isExpandable: false }, - }, - ], rows: [ { - field: 'collect_collaboration', + field: 'field1', checkbox: { - label: 'Collect folder collaboration', + label: 'checkbox list with default value true', + defaultValue: true, }, }, { - field: 'collect_file', + field: 'field2', checkbox: { - label: 'Collect file metadata', + label: 'checkbox list with default value false', + defaultValue: false, }, }, ], }, }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const searchInput = canvas.getByRole('searchbox'); - const searchText = 'file'; - await userEvent.type(searchInput, searchText, { delay: 100 }); - }, }; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json index 8af633d0c..d7ab9b4a9 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json @@ -40,7 +40,7 @@ "title": "Title example", "entity": [ { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "api3", "options": { diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json index 3878d0bdb..0ad72de7c 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json @@ -40,7 +40,7 @@ "title": "Title example", "entity": [ { - "type": "CheckboxTree", + "type": "checkboxTree", "label": "CheckboxTreeTitle", "field": "api3", "required": true, diff --git a/ui/src/components/CheckboxTree/types.ts b/ui/src/components/CheckboxTree/types.ts index 538ebbe0a..831143945 100644 --- a/ui/src/components/CheckboxTree/types.ts +++ b/ui/src/components/CheckboxTree/types.ts @@ -42,6 +42,6 @@ export interface CheckboxTreeProps { field: string, validator: (submittedField: string, submittedValue: string) => void ) => void; - handleChange: (field: string, value: string, componentType?: 'CheckboxTree') => void; + handleChange: (field: string, value: string, componentType?: 'checkboxTree') => void; disabled?: boolean; } diff --git a/ui/src/components/CheckboxTree/validation.ts b/ui/src/components/CheckboxTree/validation.ts deleted file mode 100644 index 5c5121d58..000000000 --- a/ui/src/components/CheckboxTree/validation.ts +++ /dev/null @@ -1,26 +0,0 @@ -import Validator from '../../util/Validator'; -import { Row } from './types'; - -type MaybeError = - | { - errorField: string; - errorMsg: string; - } - | false; - -/** - * Validate the required field has value - * @param {string} field - * @param {string} label - * @param {object} [rows] - * @returns {Error|false} - */ - -export function checkValidationForRequired(field: string, label: string, rows: Row[]): MaybeError { - let errorMessage: MaybeError = false; - if (!rows.some((row) => row.checkbox?.defaultValue === true)) { - errorMessage = Validator.RequiredValidator(field, label, ''); - return errorMessage; - } - return false; -} diff --git a/ui/src/constants/ControlTypeMap.ts b/ui/src/constants/ControlTypeMap.ts index bf4d95d8c..04036ee13 100644 --- a/ui/src/constants/ControlTypeMap.ts +++ b/ui/src/constants/ControlTypeMap.ts @@ -13,7 +13,7 @@ import CheckboxGroup from '../components/CheckboxGroup/CheckboxGroup'; const componentsMap = { checkbox: CheckBoxComponent, checkboxGroup: CheckboxGroup, - CheckboxTree, + checkboxTree: CheckboxTree, custom: CustomControl, file: FileInputComponent, helpLink: HelpLinkComponent, diff --git a/ui/src/types/globalConfig/entities.ts b/ui/src/types/globalConfig/entities.ts index 4e335e9d5..fd3074ced 100644 --- a/ui/src/types/globalConfig/entities.ts +++ b/ui/src/types/globalConfig/entities.ts @@ -221,7 +221,7 @@ export const CheckboxGroupEntity = CommonEditableEntityFields.extend({ }); export const CheckboxTreeEntity = CommonEditableEntityFields.extend({ - type: z.literal('CheckboxTree'), + type: z.literal('checkboxTree'), validators: z.tuple([RegexValidator]).optional(), defaultValue: z.union([z.number(), z.boolean()]).optional(), options: CommonEditableEntityOptions.extend({ From e4ccd69daeb3411c3b771137e23446afc9585a6e Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:53:58 +0000 Subject: [PATCH 14/23] update screenshots --- .../stories/__images__/CheckboxTree-base-chromium.png | 4 ++-- .../stories/__images__/CheckboxTree-create-mode-chromium.png | 4 ++-- .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-mixed-with-groups-chromium.png | 4 ++-- .../stories/__images__/CheckboxTree-multiline-chromium.png | 4 ++-- .../CheckboxTree-multiline-with-groups-chromium.png | 3 +++ .../__images__/CheckboxTree-required-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-with-single-group-chromium.png | 4 ++-- 8 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png index 2ec684184..d61b1471c 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-base-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:662dc2b2403cb24744eef669efc8ca055511a88f8f0a47c0126a2ee2c29d36c0 -size 16399 +oid sha256:e3458b2cb2f5ffe62ba39750f5546431862468e4fb484071f968ce9a62575c7d +size 14625 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png index eb1277525..fc4b60113 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-create-mode-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f1ddab7d2e13f8e058a330a99cd190285fcd9834572726ec0cfee2c69ee8d8c -size 15566 +oid sha256:700a5f90539ed4b4a0583985e92672ae68c3e526579d640a49c5d3758f531d71 +size 13605 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index 8d770411c..a98a5a32d 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0659bd14bf6a2ce8fc71db198d1bcec7e1e6503ab7e9fa41c98b2100d1db1ac5 -size 48856 +oid sha256:bb1ecd63b1176ced7621600b46a8c920ed26aff90db0b7ecdcbef806976710e5 +size 49107 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png index 236f7cf33..7e4aaa788 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb647a3678b7e691fb1e6fd15afa37dd097d737463fc517ad5dd043150c6c8b2 -size 24954 +oid sha256:a034a905c397cd3ff6ef246d2ac7afa9c4e79efd7e9f7877b7e17e01301bf56f +size 23288 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png index 3ed4555d0..b12ce7123 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a9cd8e59c6a539044148262d32293b0d9017ce2677776b95f828d9be7129b47 -size 22491 +oid sha256:5c6262e0cefaa8406bfa0fd31e179f522e1ed45e801aa8fe38c82878abafe3ca +size 20320 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png new file mode 100644 index 000000000..2612448e1 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c35b385e8cb23ed6c55c16d70ad90a34f229e54cfc880f48c099d4622895111d +size 30546 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png index 3697e3d75..8c860969c 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5bc16e1b9a0c60ec7c130f1d008efd99f851911c2c417754979ad1f341fccfe -size 40742 +oid sha256:a0182151c43d69d05b29d406e4b3386ae9a58a3722e50c41de097b41de0b533a +size 39266 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png index 943f90651..2312651e0 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1addd437737b3afcbb625878402194aa7a7b42d144019c89558ad8dc9d006d13 -size 14754 +oid sha256:7ee3d0a0a52c4b0da04ff636542022c9dd63a2dc6d30653ecf52e0395fbeec94 +size 12927 From 7f4e6f702b93801c4482f935671dd9434d189929 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Fri, 13 Dec 2024 15:26:03 +0530 Subject: [PATCH 15/23] docs: change screenshot for checkboxtree --- docs/entity/components.md | 2 +- .../checkbox_tree_mixed_example.png | Bin 75685 -> 70570 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/entity/components.md b/docs/entity/components.md index 6669f6937..d6c7fb8db 100644 --- a/docs/entity/components.md +++ b/docs/entity/components.md @@ -321,7 +321,7 @@ This is how it looks in the UI: The component maps and unmaps values into a single field in the format `fieldName1/fieldValue1,fieldName2/fieldValue2`, but only for checked rows. For the given example, it emits the following value: `rowUnderGroup1/1200,requiredField/10`. -## `checkboxTree` +## `CheckboxTree` See the following example usage: diff --git a/docs/images/components/checkbox_tree_mixed_example.png b/docs/images/components/checkbox_tree_mixed_example.png index 9e6480d5d994b6c05f446685a5582a24f3156610..30cb350531df46f896a0a9e0a9caa61cf6357e5d 100644 GIT binary patch literal 70570 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∾TopRG>Q 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

m2zY0G4i(Z1DT_rp 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%0AakEoDP&q(w|H$X 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 WJz`YU+}H Date: Fri, 13 Dec 2024 15:29:17 +0530 Subject: [PATCH 16/23] revert: delete search screenshot --- .../stories/__images__/CheckboxTree-search-chromium.png | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png deleted file mode 100644 index d82588167..000000000 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-search-chromium.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f88be5edc2d3410edf57152eea0d8e49ba044c644bf5fc7b77da84001ba4135 -size 11364 From b502f45c21e4e6c6caa57a00daf36818a01b0604 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:03:06 +0000 Subject: [PATCH 17/23] update screenshots --- .../stories/__images__/TableWrapper-ouath-basic-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png b/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png index fce79ad2c..ef8885de8 100644 --- a/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png +++ b/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9448eab903a077e23e743b7ac9ae83110415f87b97ba2225fd29e4602ed46336 -size 25254 +oid sha256:ade4bf5b82bc7ad2dd0dc437880e5a4909c421c83195af619aa46e3854b1a5ed +size 11980 From 4631fb4cf1d959cb7665ee3b83f224ee3b0a9181 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Wed, 18 Dec 2024 16:28:41 +0530 Subject: [PATCH 18/23] feat: add support for disable and prisma --- docs/entity/components.md | 13 ++- .../checkbox_tree_mixed_example.png | Bin 70570 -> 80451 bytes .../schema/schema.json | 12 +++ .../globalConfig.json | 1 + .../CheckboxTree/CheckboxSubTree.tsx | 5 +- .../components/CheckboxTree/CheckboxTree.tsx | 16 +++- .../CheckboxTree/CheckboxTreeRowWrapper.tsx | 2 +- .../CheckboxTree/StyledComponent.tsx | 80 +++++++++++++++--- .../stories/CheckboxTree.stories.tsx | 33 ++++++++ .../stories/CheckboxTreeMocks.json | 3 +- .../stories/CheckboxTreeRequiredMocks.json | 3 +- ui/src/components/CheckboxTree/types.ts | 3 +- 12 files changed, 147 insertions(+), 24 deletions(-) diff --git a/docs/entity/components.md b/docs/entity/components.md index d6c7fb8db..fbcf749ae 100644 --- a/docs/entity/components.md +++ b/docs/entity/components.md @@ -344,9 +344,10 @@ See the following example usage: "label": "Group 3", "options": { "isExpandable": true, - "expand": true + "expand": true, + "disabled": false }, - "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3"] + "fields": ["firstRowUnderGroup3", "secondRowUnderGroup3", "thirdRowUnderGroup3"] } ], "rows": [ @@ -376,6 +377,14 @@ See the following example usage: "checkbox": { "label": "second row under group 3" } + }, + { + "field": "thirdRowUnderGroup3", + "checkbox": { + "label": "third row checked under group 3", + "defaultValue": true, + "disabled": true + } } ] } diff --git a/docs/images/components/checkbox_tree_mixed_example.png b/docs/images/components/checkbox_tree_mixed_example.png index 30cb350531df46f896a0a9e0a9caa61cf6357e5d..52c9c33b3df6f06e4df98330d0f09b43456949cc 100644 GIT binary patch literal 80451 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

}5n|5*gZjxngQHjvSJr6XkvE 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∾TopRG>Q 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 = ({ checked={isParentChecked} data-indeterminate={isIndeterminate} onChange={() => handleParentCheckboxForGroup(group.label, !isParentChecked)} - disabled={disabled} + disabled={disabled || group.options?.disabled} + aria-label="custom checkbox for group" /> {group.label} @@ -68,7 +69,7 @@ const CheckboxSubTree: React.FC = ({ {group.rows.map((row) => ( { + if (disabled === true) { + return; + } setValues((prevValues) => { const updatedValues = new Map(prevValues); controlOptions.rows.forEach((row) => { + // Find the group the row belongs to + const group = controlOptions?.groups?.find((item) => + item.fields.includes(row.field) + ); + + // Skip updating if the group or the row is disabled + if (group?.options?.disabled || row.checkbox?.disabled === true) { + return; + } updatedValues.set(row.field, { checkbox: newCheckboxValue }); }); handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, - [controlOptions.rows, field, handleChange] + [controlOptions?.groups, controlOptions.rows, disabled, field, handleChange] ); return ( @@ -111,7 +123,7 @@ function CheckboxTree(props: CheckboxTreeProps) { row={row} values={values} handleRowChange={handleRowChange} - disabled={disabled} + disabled={disabled || row.checkbox?.disabled} /> ) diff --git a/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx index 7e604a2fb..865ce3b02 100644 --- a/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx @@ -20,7 +20,7 @@ function CheckboxRowWrapper({ label={row.checkbox?.label || row.field} checkbox={!!valueForField?.checkbox} handleChange={handleRowChange} - disabled={disabled} + disabled={disabled || row.checkbox?.disabled} /> ); } diff --git a/ui/src/components/CheckboxTree/StyledComponent.tsx b/ui/src/components/CheckboxTree/StyledComponent.tsx index 9eafb9cac..35abe193e 100644 --- a/ui/src/components/CheckboxTree/StyledComponent.tsx +++ b/ui/src/components/CheckboxTree/StyledComponent.tsx @@ -1,7 +1,7 @@ import styled, { css } from 'styled-components'; import ColumnLayout from '@splunk/react-ui/ColumnLayout'; import CollapsiblePanel from '@splunk/react-ui/CollapsiblePanel'; -import { variables } from '@splunk/themes'; +import { pick, variables } from '@splunk/themes'; import Switch from '@splunk/react-ui/Switch'; export const FixedCheckboxRowWidth = css` @@ -22,13 +22,24 @@ export const StyledCollapsiblePanel = styled(CollapsiblePanel)` margin-top: ${variables.spacingXSmall}; & > *:not(:last-child) { button { - background-color: ${variables.neutral300} !important; + background-color: ${pick({ + enterprise: variables.neutral300, + prisma: variables.neutral200, + })} !important; } font-size: 14px; margin-bottom: ${variables.spacingXSmall}; - background-color: ${variables.neutral300}; + background-color: ${pick({ + enterprise: variables.neutral300, + prisma: variables.neutral200, + })}; display: flex; align-items: center; + align-content: center; + // for prisma styling + & > span { + align-content: center; + } } `; @@ -43,7 +54,10 @@ export const GroupLabel = styled.div` display: flex; justify-content: space-between; padding: 6px ${variables.spacingSmall}; - background-color: ${variables.neutral300}; + background-color: ${pick({ + enterprise: variables.neutral300, + prisma: variables.neutral200, + })}; font-size: 14px; margin: ${variables.spacingSmall} 0; `; @@ -85,17 +99,26 @@ export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` min-width: 20px; height: 20px; min-height: 20px; - border: 2px solid #6b7280; - border-radius: 4px; - background-color: #ffffff; + border-radius: 2px; + background-color: ${pick({ + enterprise: variables.backgroundColor, + prisma: variables.backgroundColor, + })}; cursor: pointer; display: inline-block; - margin-right: 8px; position: relative; + border: 1px solid + ${pick({ + enterprise: variables.contentColorMuted, + prisma: variables.contentColorMuted, + })}; &:checked { - background-color: #ffffff; - border: 2px solid #6b7280; + background-color: ${pick({ + enterprise: variables.backgroundColor, + prisma: variables.focusColor, + })}; + border: none; } &:checked::after { @@ -103,7 +126,10 @@ export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` position: absolute; width: 12px; height: 12px; - background-color: #6b7280; + background-color: ${pick({ + enterprise: variables.textColor, + prisma: variables.white, + })}; clip-path: polygon( 35.75% 85.22%, 100% 8.1%, @@ -117,12 +143,22 @@ export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` transform: translate(-50%, -50%); } + &[data-indeterminate='true'] { + background-color: ${pick({ + enterprise: variables.backgroundColor, + prisma: variables.focusColor, + })}; + } + &[data-indeterminate='true']::after { content: ''; position: absolute; width: 10px; height: 2px; - background-color: #6b7280; + background-color: ${pick({ + enterprise: variables.textColor, + prisma: variables.white, + })}; top: 50%; left: 50%; transform: translate(-50%, -50%); @@ -130,8 +166,24 @@ export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` } &:disabled { - border-color: #d1d5db; - background-color: #f9fafb; + border: 1px solid ${variables.borderColorWeak}; + border: 1px solid + ${pick({ + enterprise: variables.borderColorWeak, + prisma: variables.contentColorDisabled, + })}; + background-color: ${pick({ + enterprise: variables.backgroundColor, + prisma: variables.backgroundColorDialog, + })}; cursor: not-allowed; } + + &:disabled[data-indeterminate='true']::after, + &:disabled[checked]::after { + background-color: ${pick({ + enterprise: variables.borderColorWeak, + prisma: variables.contentColorMuted, + })}; + } `; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx index 3bdd8896a..827c700e9 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx +++ b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx @@ -225,3 +225,36 @@ export const CreateMode: Story = { }, }, }; + +export const Disabled: Story = { + args: { + ...Base.args, + value: undefined, + mode: MODE_CREATE, + disabled: true, + controlOptions: { + groups: [ + { + label: 'Group 1', + fields: ['collect_collaboration', 'collect_file'], + options: { isExpandable: false }, + }, + ], + rows: [ + { + field: 'collect_collaboration', + checkbox: { + label: 'Collect folder collaboration', + defaultValue: true, + }, + }, + { + field: 'collect_file', + checkbox: { + label: 'Collect file metadata', + }, + }, + ], + }, + }, +}; diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json index d7ab9b4a9..2e26d8a7d 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeMocks.json @@ -93,7 +93,8 @@ "field": "firstRowUnderGroup3", "checkbox": { "label": "first row checked under group 3", - "defaultValue": true + "defaultValue": true, + "disabled": true } }, { diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json index 0ad72de7c..bd56a165d 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json +++ b/ui/src/components/CheckboxTree/stories/CheckboxTreeRequiredMocks.json @@ -88,7 +88,8 @@ { "field": "secondRowUnderGroup3", "checkbox": { - "label": "second row under group 3" + "label": "second row under group 3", + "disabled": true } } ] diff --git a/ui/src/components/CheckboxTree/types.ts b/ui/src/components/CheckboxTree/types.ts index 831143945..fe70f78da 100644 --- a/ui/src/components/CheckboxTree/types.ts +++ b/ui/src/components/CheckboxTree/types.ts @@ -3,7 +3,6 @@ import { Mode } from '../../constants/modes'; export type Field = string; export type Value = { checkbox: boolean; - inputValue?: number; error?: string; }; @@ -15,6 +14,7 @@ export interface Group { options?: { isExpandable?: boolean; expand?: boolean; + disabled?: boolean; }; } @@ -23,6 +23,7 @@ export interface Row { checkbox?: { label?: string; defaultValue?: boolean; + disabled?: boolean; }; } From 76119659dbba8e0b39b6c1e6d1d44a9edf1f2f42 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:04:05 +0000 Subject: [PATCH 19/23] update screenshots --- .../stories/__images__/CheckboxTree-disabled-chromium.png | 3 +++ .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-mixed-with-groups-chromium.png | 4 ++-- .../CheckboxTree-multiline-with-groups-chromium.png | 4 ++-- .../__images__/CheckboxTree-required-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-with-single-group-chromium.png | 4 ++-- .../stories/__images__/TableWrapper-ouath-basic-chromium.png | 4 ++-- 7 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-disabled-chromium.png diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-disabled-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-disabled-chromium.png new file mode 100644 index 000000000..2198f4901 --- /dev/null +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-disabled-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1658edbc3a9a08bae521e77311ff37ca3dcf67dd8264c7692b1e4612cfdd78c5 +size 11862 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index a98a5a32d..1a1376bd1 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb1ecd63b1176ced7621600b46a8c920ed26aff90db0b7ecdcbef806976710e5 -size 49107 +oid sha256:5497542b2583fc8554905855792175228360d2461cb48e0c195e6f98e6f5e4de +size 48088 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png index 7e4aaa788..ab6ed0b11 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-mixed-with-groups-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a034a905c397cd3ff6ef246d2ac7afa9c4e79efd7e9f7877b7e17e01301bf56f -size 23288 +oid sha256:1175f52e83827f3a4cec037971c248ff046e37b138f999242ed2cbace7ac00a1 +size 23013 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png index 2612448e1..d7c9e18f8 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-multiline-with-groups-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c35b385e8cb23ed6c55c16d70ad90a34f229e54cfc880f48c099d4622895111d -size 30546 +oid sha256:73edc138700979617bc47324814c42e04a380134e69aec3af3e97f1310289b05 +size 30270 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png index 8c860969c..4eff9920b 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0182151c43d69d05b29d406e4b3386ae9a58a3722e50c41de097b41de0b533a -size 39266 +oid sha256:0723a8f4159b4598755522871a51a38e5f84a9ff98914aef53ddfe5ba29287cb +size 38541 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png index 2312651e0..df3a991af 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-with-single-group-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ee3d0a0a52c4b0da04ff636542022c9dd63a2dc6d30653ecf52e0395fbeec94 -size 12927 +oid sha256:cc19b9b5f1442b71a2419f905171dfcdec9bfb46412e9d59c05aa1bc121e0300 +size 12793 diff --git a/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png b/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png index ef8885de8..fce79ad2c 100644 --- a/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png +++ b/ui/src/components/table/stories/__images__/TableWrapper-ouath-basic-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ade4bf5b82bc7ad2dd0dc437880e5a4909c421c83195af619aa46e3854b1a5ed -size 11980 +oid sha256:9448eab903a077e23e743b7ac9ae83110415f87b97ba2225fd29e4602ed46336 +size 25254 From d8b278ca415440fbb1c12ca40460acc528b1beb4 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Wed, 18 Dec 2024 22:59:20 +0530 Subject: [PATCH 20/23] fix: resolve issue for disable in group checkbox --- ui/src/components/CheckboxTree/CheckboxTree.tsx | 6 ++++++ ui/src/components/CheckboxTree/StyledComponent.tsx | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/src/components/CheckboxTree/CheckboxTree.tsx b/ui/src/components/CheckboxTree/CheckboxTree.tsx index a84d85868..cb7c378f1 100644 --- a/ui/src/components/CheckboxTree/CheckboxTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTree.tsx @@ -67,6 +67,12 @@ function CheckboxTree(props: CheckboxTreeProps) { setValues((prevValues) => { const updatedValues = new Map(prevValues); group.fields.forEach((item) => { + const findFieldInRow = controlOptions?.rows?.find( + (rowItem) => rowItem.field === item + ); + if (findFieldInRow?.checkbox?.disabled === true) { + return; + } updatedValues.set(item, { checkbox: newCheckboxValue }); }); handleChange(field, packValue(updatedValues), 'checkboxTree'); diff --git a/ui/src/components/CheckboxTree/StyledComponent.tsx b/ui/src/components/CheckboxTree/StyledComponent.tsx index 35abe193e..3734d9fc6 100644 --- a/ui/src/components/CheckboxTree/StyledComponent.tsx +++ b/ui/src/components/CheckboxTree/StyledComponent.tsx @@ -35,7 +35,6 @@ export const StyledCollapsiblePanel = styled(CollapsiblePanel)` })}; display: flex; align-items: center; - align-content: center; // for prisma styling & > span { align-content: center; @@ -118,7 +117,6 @@ export const CustomCheckbox = styled.input.attrs({ type: 'checkbox' })` enterprise: variables.backgroundColor, prisma: variables.focusColor, })}; - border: none; } &:checked::after { From ac152cfad1381ced23b92ebb6660f6b9dbd207b4 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:33:16 +0000 Subject: [PATCH 21/23] update screenshots --- .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index 1a1376bd1..d43b78d6b 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5497542b2583fc8554905855792175228360d2461cb48e0c195e6f98e6f5e4de -size 48088 +oid sha256:571d9b678d9ba9695fd8f38f997ec632933f8dd9e3ecac324f017fa006776f78 +size 48151 From e9fe08e6d1a4c5a068e0052878c453c48e91dca0 Mon Sep 17 00:00:00 2001 From: rohanm-crest Date: Thu, 26 Dec 2024 16:33:53 +0530 Subject: [PATCH 22/23] revert(disable): remove the disable, disableonedit is already present --- .../globalConfig.json | 1 - .../CheckboxTree/CheckboxSubTree.tsx | 6 ++-- .../components/CheckboxTree/CheckboxTree.tsx | 21 ++---------- .../CheckboxTree/CheckboxTreeRowWrapper.tsx | 2 +- .../stories/CheckboxTree.stories.tsx | 33 ------------------- ui/src/components/CheckboxTree/types.ts | 4 +-- 6 files changed, 8 insertions(+), 59 deletions(-) diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index d6048baac..335470e2c 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1577,7 +1577,6 @@ "label": "Event Filters", "field": "checkbox_field", "required": true, - "disabled": false, "options": { "groups": [ { diff --git a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx index 4fafafb4a..1a3bb0373 100644 --- a/ui/src/components/CheckboxTree/CheckboxSubTree.tsx +++ b/ui/src/components/CheckboxTree/CheckboxSubTree.tsx @@ -57,8 +57,8 @@ const CheckboxSubTree: React.FC = ({ checked={isParentChecked} data-indeterminate={isIndeterminate} onChange={() => handleParentCheckboxForGroup(group.label, !isParentChecked)} - disabled={disabled || group.options?.disabled} - aria-label="custom checkbox for group" + disabled={disabled} + aria-label="custom checkbox to manage select/deselect/indeterminate state" /> {group.label} @@ -69,7 +69,7 @@ const CheckboxSubTree: React.FC = ({ {group.rows.map((row) => ( { const updatedValues = new Map(prevValues); group.fields.forEach((item) => { - const findFieldInRow = controlOptions?.rows?.find( - (rowItem) => rowItem.field === item - ); - if (findFieldInRow?.checkbox?.disabled === true) { - return; - } updatedValues.set(item, { checkbox: newCheckboxValue }); }); handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, - [controlOptions, field, handleChange] + [controlOptions.groups, field, handleChange] ); const handleCheckboxToggleAll = useCallback( @@ -90,22 +84,13 @@ function CheckboxTree(props: CheckboxTreeProps) { setValues((prevValues) => { const updatedValues = new Map(prevValues); controlOptions.rows.forEach((row) => { - // Find the group the row belongs to - const group = controlOptions?.groups?.find((item) => - item.fields.includes(row.field) - ); - - // Skip updating if the group or the row is disabled - if (group?.options?.disabled || row.checkbox?.disabled === true) { - return; - } updatedValues.set(row.field, { checkbox: newCheckboxValue }); }); handleChange(field, packValue(updatedValues), 'checkboxTree'); return updatedValues; }); }, - [controlOptions?.groups, controlOptions.rows, disabled, field, handleChange] + [controlOptions.rows, disabled, field, handleChange] ); return ( @@ -129,7 +114,7 @@ function CheckboxTree(props: CheckboxTreeProps) { row={row} values={values} handleRowChange={handleRowChange} - disabled={disabled || row.checkbox?.disabled} + disabled={disabled} /> ) diff --git a/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx index 865ce3b02..7e604a2fb 100644 --- a/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx +++ b/ui/src/components/CheckboxTree/CheckboxTreeRowWrapper.tsx @@ -20,7 +20,7 @@ function CheckboxRowWrapper({ label={row.checkbox?.label || row.field} checkbox={!!valueForField?.checkbox} handleChange={handleRowChange} - disabled={disabled || row.checkbox?.disabled} + disabled={disabled} /> ); } diff --git a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx index 827c700e9..3bdd8896a 100644 --- a/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx +++ b/ui/src/components/CheckboxTree/stories/CheckboxTree.stories.tsx @@ -225,36 +225,3 @@ export const CreateMode: Story = { }, }, }; - -export const Disabled: Story = { - args: { - ...Base.args, - value: undefined, - mode: MODE_CREATE, - disabled: true, - controlOptions: { - groups: [ - { - label: 'Group 1', - fields: ['collect_collaboration', 'collect_file'], - options: { isExpandable: false }, - }, - ], - rows: [ - { - field: 'collect_collaboration', - checkbox: { - label: 'Collect folder collaboration', - defaultValue: true, - }, - }, - { - field: 'collect_file', - checkbox: { - label: 'Collect file metadata', - }, - }, - ], - }, - }, -}; diff --git a/ui/src/components/CheckboxTree/types.ts b/ui/src/components/CheckboxTree/types.ts index fe70f78da..9de9f4632 100644 --- a/ui/src/components/CheckboxTree/types.ts +++ b/ui/src/components/CheckboxTree/types.ts @@ -14,7 +14,6 @@ export interface Group { options?: { isExpandable?: boolean; expand?: boolean; - disabled?: boolean; }; } @@ -23,7 +22,6 @@ export interface Row { checkbox?: { label?: string; defaultValue?: boolean; - disabled?: boolean; }; } @@ -43,6 +41,6 @@ export interface CheckboxTreeProps { field: string, validator: (submittedField: string, submittedValue: string) => void ) => void; - handleChange: (field: string, value: string, componentType?: 'checkboxTree') => void; + handleChange: (field: string, value: string, componentType: 'checkboxTree') => void; disabled?: boolean; } From 7fbbbea0c03b6af7d9c7aba665ae5cf6bbe12a3e Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:08:00 +0000 Subject: [PATCH 23/23] update screenshots --- .../__images__/CheckboxTree-input-page-view-chromium.png | 4 ++-- .../__images__/CheckboxTree-required-view-chromium.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png index d43b78d6b..c1dee3187 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-input-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:571d9b678d9ba9695fd8f38f997ec632933f8dd9e3ecac324f017fa006776f78 -size 48151 +oid sha256:c2bbc2f0024e857c6aabc0b66ec765c4a3864e0e59280c8135ac2bde3859fc1f +size 48846 diff --git a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png index 4eff9920b..b938474e8 100644 --- a/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png +++ b/ui/src/components/CheckboxTree/stories/__images__/CheckboxTree-required-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0723a8f4159b4598755522871a51a38e5f84a9ff98914aef53ddfe5ba29287cb -size 38541 +oid sha256:a4c9757884236615689fe834219f48ee62b0230b0f7ec03f9b2ec43e7b6aec3a +size 39026