Skip to content

Commit

Permalink
refactor: add Typescript support for some files (#1303)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohanm-crest authored Aug 21, 2024
1 parent 5cafab6 commit 32c34e5
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 219 deletions.
5 changes: 4 additions & 1 deletion ui/src/components/ControlWrapper/ControlWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CONTROL_TYPE_MAP, { ComponentTypes } from '../../constants/ControlTypeMap
import { AnyEntity, UtilControlWrapper } from '../BaseFormView/BaseFormTypes';
import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes';
import CustomControl from '../CustomControl/CustomControl';
import { Mode } from '../../constants/modes';

const CustomElement = styled.div``;

Expand All @@ -26,7 +27,7 @@ const ControlGroupWrapper = styled(ControlGroup).attrs((props: { dataName: strin
`;

interface ControlWrapperProps {
mode: string;
mode: Mode;
utilityFuncts: UtilControlWrapper;
value: AcceptableFormValueOrNullish;
display: boolean;
Expand Down Expand Up @@ -130,6 +131,7 @@ class ControlWrapper extends React.PureComponent<ControlWrapperProps> {
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 || '';

return (
this.props.display && (
Expand All @@ -142,6 +144,7 @@ class ControlWrapper extends React.PureComponent<ControlWrapperProps> {
dataName={this?.props?.entity.field}
labelWidth={240}
required={isFieldRequired}
label={label}
>
<CustomElement>{rowView}</CustomElement>
</ControlGroupWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { _ } from '@splunk/ui-utils/i18n';

import { getUnifiedConfigs } from '../util/util';
import { getBuildDirPath } from '../util/script';
import { AcceptableFormValueOrNullish } from '../types/components/shareableTypes';
import { UtilBaseForm } from './BaseFormView/BaseFormTypes';
import { GlobalConfig } from '../types/globalConfig/globalConfig';
import { Mode } from '../constants/modes';

class CustomControl extends Component {
static loadCustomControl = (module, type, appName) =>
interface IData {
value: AcceptableFormValueOrNullish;
mode: Mode;
serviceName: string;
}

interface ICustomCompClass {
new (
config: GlobalConfig,
data: IData,
setValue: (field: string, newValue: AcceptableFormValueOrNullish) => void,
util: UtilBaseForm,
el?: HTMLElement
): {
render: () => void;
validation?: (submittedField: string, submittedValue: string) => void;
};
}

interface ICustomCompProps {
data: IData;
field: string;
handleChange: (field: string, newValue: AcceptableFormValueOrNullish) => void;
controlOptions: { src: string; type: string };
addCustomValidator: (
field: string,
validatorFunc: (submittedField: string, submittedValue: string) => void
) => void;
utilCustomFunctions: UtilBaseForm;
}

interface State {
loading: boolean;
}

class CustomControl extends Component<ICustomCompProps, State> {
static loadCustomControl = (
module: string,
type: string,
appName: string
): Promise<ICustomCompClass> =>
new Promise((resolve) => {
if (type === 'external') {
import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then(
Expand All @@ -16,13 +58,18 @@ class CustomControl extends Component {
}
);
} else {
// @ts-expect-error typeof __non_webpack_require__ is not known during bundle
__non_webpack_require__([`app/${appName}/js/build/custom/${module}`], (Control) => {
resolve(Control);
});
}
});

constructor(props) {
shouldRender: boolean;

el?: HTMLElement;

constructor(props: ICustomCompProps) {
super(props);
this.state = {
loading: true,
Expand All @@ -41,10 +88,10 @@ class CustomControl extends Component {
).then((Control) => {
const customControl = new Control(
globalConfig,
this.el,
this.props.data,
this.setValue,
this.props.utilCustomFunctions
this.props.utilCustomFunctions,
this.el
);
customControl.render();

Expand All @@ -55,15 +102,15 @@ class CustomControl extends Component {
});
}

shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: ICustomCompProps, nextState: State) {
if (!nextState.loading && this.shouldRender) {
this.shouldRender = false;
return true;
}
return false;
}

setValue = (newValue) => {
setValue = (newValue: AcceptableFormValueOrNullish) => {
this.props.handleChange(this.props.field, newValue);
};

Expand All @@ -74,7 +121,9 @@ class CustomControl extends Component {
{
<span // nosemgrep: typescript.react.security.audit.react-no-refs.react-no-refs
ref={(el) => {
this.el = el;
if (el) {
this.el = el;
}
}}
style={{ visibility: this.state.loading ? 'hidden' : 'visible' }}
/>
Expand All @@ -84,13 +133,4 @@ class CustomControl extends Component {
}
}

CustomControl.propTypes = {
data: PropTypes.object,
field: PropTypes.string,
handleChange: PropTypes.func,
controlOptions: PropTypes.object,
addCustomValidator: PropTypes.func,
utilCustomFunctions: PropTypes.object,
};

export default CustomControl;
13 changes: 7 additions & 6 deletions ui/src/components/CustomControl/CustomControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ import { getBuildDirPath } from '../../util/script';
import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes';
import { UtilBaseForm } from '../BaseFormView/BaseFormTypes';
import { GlobalConfig } from '../../types/globalConfig/globalConfig';
import { Mode } from '../../constants/modes';

interface IData {
value: AcceptableFormValueOrNullish;
mode: string;
mode: Mode;
serviceName: string;
}

interface ICustomCompClass {
new (
config: GlobalConfig,
el: HTMLElement | undefined,
data: IData,
setValue: (field: string, newValue: AcceptableFormValueOrNullish) => void,
util: UtilBaseForm
util: UtilBaseForm,
el?: HTMLElement
): {
render: () => void;
validation?: (submittedField: string, submittedValue: string) => void;
Expand Down Expand Up @@ -83,13 +84,13 @@ class CustomControl extends React.Component<ICustomCompProps, ICustomCompState>
this.props.controlOptions.src,
this.props.controlOptions.type,
appName
).then((Control: ICustomCompClass) => {
).then((Control) => {
const customControl = new Control(
globalConfig,
this.el,
this.props.data,
this.setValue,
this.props.utilCustomFunctions
this.props.utilCustomFunctions,
this.el
);
customControl?.render();

Expand Down
10 changes: 7 additions & 3 deletions ui/src/components/CustomControl/CustomControlMockForTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { UtilControlWrapper } from '../BaseFormView/BaseFormTypes';
export class CustomControlMockForTest {
globalConfig: GlobalConfig;

el: Element;
el?: Element;

data: { mode: Mode; serviceName: string; value: AcceptableFormValueOrNullish };

Expand All @@ -25,10 +25,10 @@ export class CustomControlMockForTest {
*/
constructor(
globalConfig: GlobalConfig,
el: Element,
data: { mode: Mode; serviceName: string; value: AcceptableFormValueOrNullish },
setValue: (newValue: AcceptableFormValueOrNullish) => void,
util: UtilControlWrapper
util: UtilControlWrapper,
el?: Element
) {
this.globalConfig = globalConfig;
this.el = el;
Expand All @@ -53,6 +53,10 @@ export class CustomControlMockForTest {
}

render() {
if (!this.el) {
return this;
}

const options = ['input_default', 'input_one', 'input_two', 'input_three'].map(
(value) =>
`<option value="${value}" ${
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { _ } from '@splunk/ui-utils/i18n';
import { z } from 'zod';
import { getUnifiedConfigs } from '../../util/util';
import { getBuildDirPath } from '../../util/script';
import { TabSchema } from '../../types/globalConfig/pages';

function CustomTab({ tab }) {
type Tab = z.infer<typeof TabSchema>;

interface CustomTabProps {
tab: Tab;
}

interface ICustomTabClass {
new (tab: Tab, ref: HTMLDivElement): {
render: () => void;
};
}

const CustomTab: React.FC<CustomTabProps> = ({ tab }) => {
const [loading, setLoading] = useState(true);
const divRef = useRef(null);
const divRef = useRef<HTMLDivElement>(null);

const globalConfig = getUnifiedConfigs();
const appName = globalConfig.meta.name;

const loadCustomTab = () =>
const loadCustomTab = (): Promise<ICustomTabClass> =>
new Promise((resolve) => {
if (tab.customTab.type === 'external') {
if (tab.customTab?.type === 'external') {
import(
/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${tab.customTab.src}.js`
).then((external) => {
const Control = external.default;
resolve(Control);
});
} else {
// @ts-expect-error should be exported to other js module and imported here
__non_webpack_require__(
[`app/${appName}/js/build/custom/${tab.customTab.src}`],
(Control) => resolve(Control)
[`app/${appName}/js/build/custom/${tab.customTab?.src}`],
(Control: ICustomTabClass) => resolve(Control)
);
}
});

useEffect(() => {
loadCustomTab().then((Control) => {
const customControl = new Control(tab, divRef.current);
customControl.render();
setLoading(false);
if (divRef.current) {
const customControl = new Control(tab, divRef.current);
customControl.render();
setLoading(false);
}
});
}, []); // eslint-disable-line react-hooks/exhaustive-deps

Expand All @@ -42,10 +58,6 @@ function CustomTab({ tab }) {
<div ref={divRef} style={{ visibility: loading ? 'hidden' : 'visible' }} />
</>
);
}

CustomTab.propTypes = {
tab: PropTypes.object.isRequired,
};

export default CustomTab;
Loading

0 comments on commit 32c34e5

Please sign in to comment.