Skip to content

Commit

Permalink
feat: add support for disableonEdit in oauth (#953)
Browse files Browse the repository at this point in the history
* feat: add support for disableonEdit in oauth

* feat: add tests for EntityModal disableonEdit

* fix: removed placeholder for test data

* fix: correct displayed text for test field

* chore: review

* fix: fix tests, wrap tabs in array

* fix: make custom field optional

* fix: add field to expected addon

* fix: add rest field to expected addon

* fix: remove fields from expected addon

* fix: fix types in tests
  • Loading branch information
soleksy-splunk authored Nov 28, 2023
1 parent 58ab2e0 commit d948128
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 25 deletions.
2 changes: 2 additions & 0 deletions docs/advanced/oauth_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Auth can be used inside the entity tag. Use `type: "oauth"` in the entity list a
- `required`: To specify whether the field is required or not. The default value is true.
- `options`:
- `placeholder`: The placeholder for the field.
- `disableonEdit`: When the form is in edit mode, the field becomes uneditable. Default value: false


> [!WARNING]
> [Placeholder](https://splunkui.splunkeng.com/Packages/react-ui/Text?section=develop) attribute is deprecated and will be removed in one of the following versions. Instead, we recommend using "help" attribute.
Expand Down
3 changes: 3 additions & 0 deletions splunk_add_on_ucc_framework/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,9 @@
"placeholder": {
"type": "string",
"maxLength": 250
},
"disableonEdit": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ config2_help_link =
username =
password =
token =
basic_oauth_text =
client_id =
client_secret =
redirect_url =
oauth_oauth_text =
access_token =
refresh_token =
instance_url =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@
default=None,
validator=None
),
field.RestField(
'basic_oauth_text',
required=False,
encrypted=False,
default=None,
validator=None
),
field.RestField(
'client_id',
required=False,
Expand All @@ -113,6 +120,13 @@
default=None,
validator=None
),
field.RestField(
'oauth_oauth_text',
required=False,
encrypted=False,
default=None,
validator=None
),
field.RestField(
'access_token',
required=False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@
"encrypted": true,
"help": "Enter the security token.",
"field": "token"
},
{
"oauth_field": "some_text",
"label": "Disabled on edit for oauth",
"help": "Enter text for field disabled on edit",
"field": "basic_oauth_text",
"required": false,
"options": {
"disableonEdit": true
}
}
],
"oauth": [
Expand All @@ -173,6 +183,16 @@
"label": "Redirect url",
"field": "redirect_url",
"help": "Copy and paste this URL into your app."
},
{
"oauth_field": "oauth_some_text",
"label": "Disabled on edit for oauth",
"help": "Enter text for field disabled on edit",
"field": "oauth_oauth_text",
"required": false,
"options": {
"disableonEdit": true
}
}
],
"auth_code_endpoint": "/services/oauth2/authorize",
Expand Down Expand Up @@ -1318,7 +1338,7 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.32.0Rde74f1a8",
"version": "5.32.0R3be5ef5e",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.3"
}
Expand Down
33 changes: 20 additions & 13 deletions ui/src/components/BaseFormView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class BaseFormView extends PureComponent {
);
}
if (props.mode === MODE_EDIT || props.mode === MODE_CLONE) {
this.currentInput = context.rowData[props.serviceName][props.stanzaName];
this.currentInput =
context?.rowData?.[props.serviceName]?.[props.stanzaName];
}
}
});
Expand All @@ -100,12 +101,13 @@ class BaseFormView extends PureComponent {
);
}
if (tab.table && (props.mode === MODE_EDIT || props.mode === MODE_CLONE)) {
this.currentInput = context.rowData[props.serviceName][props.stanzaName];
this.currentInput =
context?.rowData?.[props.serviceName]?.[props.stanzaName];
} else if (props.mode === MODE_CONFIG) {
this.currentInput = props.currentServiceState;
this.mode_config_title = tab.title;
} else {
this.currentInput = context.rowData[props.serviceName];
this.currentInput = context?.rowData?.[props.serviceName];
}
}
});
Expand All @@ -132,8 +134,8 @@ class BaseFormView extends PureComponent {
// Defining state for auth_type in case of multiple Authentication
const tempEntity = {};
tempEntity.value =
typeof this.currentInput.auth_type !== 'undefined'
? this.currentInput.auth_type
typeof this.currentInput?.auth_type !== 'undefined'
? this.currentInput?.auth_type
: authType[0];
tempEntity.display = true;
tempEntity.error = false;
Expand Down Expand Up @@ -183,14 +185,19 @@ class BaseFormView extends PureComponent {
: false;
tempEntity.value = isEncrypted
? ''
: this.currentInput[field.field];
: this.currentInput?.[field.field];
}
tempEntity.display =
typeof temState.auth_type !== 'undefined'
? type === temState.auth_type.value
: true;
tempEntity.error = false;
tempEntity.disabled = false;

if (props.mode === MODE_EDIT) {
tempEntity.disabled = field?.options?.disableonEdit || false;
}

temState[field.field] = tempEntity;
// eslint-disable-next-line no-param-reassign
field.type =
Expand Down Expand Up @@ -261,8 +268,8 @@ class BaseFormView extends PureComponent {
temState[e.field] = tempEntity;
} else if (props.mode === MODE_EDIT) {
tempEntity.value =
typeof this.currentInput[e.field] !== 'undefined'
? this.currentInput[e.field]
typeof this.currentInput?.[e.field] !== 'undefined'
? this.currentInput?.[e.field]
: null;
tempEntity.value = e.encrypted ? '' : tempEntity.value;
tempEntity.display =
Expand All @@ -277,7 +284,7 @@ class BaseFormView extends PureComponent {
temState[e.field] = tempEntity;
} else if (props.mode === MODE_CLONE) {
tempEntity.value =
e.field === 'name' || e.encrypted ? '' : this.currentInput[e.field];
e.field === 'name' || e.encrypted ? '' : this.currentInput?.[e.field];
tempEntity.display =
typeof e?.options?.display !== 'undefined' ? e.options.display : true;
tempEntity.error = false;
Expand All @@ -286,8 +293,8 @@ class BaseFormView extends PureComponent {
} else if (props.mode === MODE_CONFIG) {
e.defaultValue = typeof e.defaultValue !== 'undefined' ? e.defaultValue : null;
tempEntity.value =
typeof this.currentInput[e.field] !== 'undefined'
? this.currentInput[e.field]
typeof this.currentInput?.[e.field] !== 'undefined'
? this.currentInput?.[e.field]
: e.defaultValue;
tempEntity.value = e.encrypted ? '' : tempEntity.value;
tempEntity.display =
Expand Down Expand Up @@ -412,7 +419,7 @@ class BaseFormView extends PureComponent {
// validation for unique name
if ([MODE_CREATE, MODE_CLONE].includes(this.props.mode)) {
const isExistingName = Boolean(
Object.values(this.context.rowData).find((val) =>
Object.values(this.context?.rowData).find((val) =>
Object.keys(val).find((name) => name === this.datadict.name)
)
);
Expand Down Expand Up @@ -637,7 +644,7 @@ class BaseFormView extends PureComponent {
};

this.context.setRowData(
update(this.context.rowData, {
update(this.context?.rowData, {
[this.props.serviceName]: { $merge: tmpObj },
})
);
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/ConfigurationTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';

import { TableContextProvider } from '../context/TableContext';
import TableWrapper from './table/TableWrapper';
import EntityModal from './EntityModal';
import EntityModal from './EntityModal/EntityModal';
import EntityPage from './EntityPage';
import { MODE_CREATE, MODE_CLONE } from '../constants/modes';
import { PAGE_CONF } from '../constants/pages';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import EntityModal from './EntityModal';
import { setUnifiedConfig } from '../util/util';
import { getGlobalConfigMock } from '../mocks/globalConfigMock';
import { setUnifiedConfig } from '../../util/util';
import { getGlobalConfigMock } from '../../mocks/globalConfigMock';

const meta = {
component: EntityModal,
title: 'Components/EntityModal',
render: (props) => {
// TODO: introduce a stateless stories component to reflect thaat component logic itself
setUnifiedConfig(getGlobalConfigMock());
setUnifiedConfig(getGlobalConfigMock()); // eslint-disable-line no-use-before-define

return <EntityModal {...props} />;
},
} satisfies Meta<typeof EntityModal>;
Expand Down
112 changes: 112 additions & 0 deletions ui/src/components/EntityModal/EntityModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react';
import { render } from '@testing-library/react';
import EntityModal, { EntityModalProps } from './EntityModal';
import { setUnifiedConfig } from '../../util/util';
import { getConfigBasicOauthDisableonEdit, getConfigOauthOauthDisableonEdit } from './TestConfig';

describe('EntityModal - Basic oauth', () => {
const handleRequestClose = jest.fn();

const setUpConfigWithDisabedBasicOauth = () => {
setUnifiedConfig(getConfigBasicOauthDisableonEdit());
};

const renderModalWithProps = (props: EntityModalProps) => {
render(<EntityModal {...props} handleRequestClose={handleRequestClose} />);
};

const getDisabledBasicField = () =>
document.getElementsByClassName('basic_oauth_text_jest_test')[1];

it('if oauth field not disabled with create after disableonEdit true', async () => {
setUpConfigWithDisabedBasicOauth();
const props = {
serviceName: 'account',
mode: 'create',
stanzaName: undefined,
formLabel: 'formLabel',
page: 'configuration',
groupName: '',
open: true,
handleRequestClose: () => {},
} satisfies EntityModalProps;
renderModalWithProps(props);
const oauthTextBox = getDisabledBasicField();
expect(oauthTextBox).toBeInTheDocument();
expect(oauthTextBox?.getAttribute('disabled')).toBeNull();
});

it('test if oauth field disabled on edit after disableonEdit true', async () => {
setUpConfigWithDisabedBasicOauth();
const props = {
serviceName: 'account',
mode: 'edit',
stanzaName: undefined,
formLabel: 'formLabel',
page: 'configuration',
groupName: '',
open: true,
handleRequestClose: () => {},
} satisfies EntityModalProps;

renderModalWithProps(props);

const oauthTextBox = getDisabledBasicField();
expect(oauthTextBox).toBeInTheDocument();
expect(oauthTextBox?.getAttribute('disabled')).toBe('');
});
});

describe('EntityModal - Oauth oauth', () => {
const handleRequestClose = jest.fn();

const setUpConfigWithDisabedOauth = () => {
const newConfig = getConfigOauthOauthDisableonEdit();
setUnifiedConfig(newConfig);
};

const renderModalWithProps = (props: EntityModalProps) => {
render(<EntityModal {...props} handleRequestClose={handleRequestClose} />);
};

const getDisabledOauthField = () =>
document.getElementsByClassName('oauth_oauth_text_jest_test')[1];

it('Oauth Oauth - test if oauth field not disabled on create after disableonEdit', async () => {
setUpConfigWithDisabedOauth();
const props = {
serviceName: 'account',
mode: 'create',
stanzaName: undefined,
formLabel: 'formLabel',
page: 'configuration',
groupName: '',
open: true,
handleRequestClose: () => {},
} satisfies EntityModalProps;
renderModalWithProps(props);
const oauthTextBox = getDisabledOauthField();
expect(oauthTextBox).toBeInTheDocument();
expect(oauthTextBox?.getAttribute('disabled')).toBeNull();
});

it('Oauth Oauth - test if oauth field disabled on edit after disableonEdit', async () => {
setUpConfigWithDisabedOauth();
const props = {
serviceName: 'account',
mode: 'edit',
stanzaName: undefined,
formLabel: 'formLabel',
page: 'configuration',
groupName: '',
open: true,
handleRequestClose: () => {},
} satisfies EntityModalProps;

renderModalWithProps(props);

const oauthTextBox = getDisabledOauthField();
expect(oauthTextBox).toBeInTheDocument();
expect(oauthTextBox?.getAttribute('disabled')).toBe('');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import styled from 'styled-components';
import WaitSpinner from '@splunk/react-ui/WaitSpinner';
import { _ } from '@splunk/ui-utils/i18n';

import { MODE_CLONE, MODE_CREATE, MODE_EDIT } from '../constants/modes';
import { StyledButton } from '../pages/EntryPageStyle';
import BaseFormView from './BaseFormView';
import { MODE_CLONE, MODE_CREATE, MODE_EDIT } from '../../constants/modes';
import { StyledButton } from '../../pages/EntryPageStyle';
import BaseFormView from '../BaseFormView';

const ModalWrapper = styled(Modal)`
width: 800px;
`;

interface EntityModalProps {
export interface EntityModalProps {
page: string;
mode: string;
serviceName: string;
Expand Down
Loading

0 comments on commit d948128

Please sign in to comment.