Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: preparation to NPM publish #1511

Merged
merged 11 commits into from
Jan 10, 2025
2 changes: 2 additions & 0 deletions .github/workflows/build-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
run: yarn run lint
- name: Unit test
run: yarn run test
- name: Build UCC library
run: yarn run build:lib
- name: Build UCC UI
run: yarn run build
- name: List deps into dependencies.txt
Expand Down
27 changes: 22 additions & 5 deletions ui/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{
"name": "@splunk/ucc_ui_lib",
"version": "0.0.1",
"name": "@splunk/add-on-ucc-framework",
"description": "UCC framework is a build and code generation framework for building Splunk Add-ons (TAs)",
"repository": {
"type": "git",
"url": "git+https://github.com/splunk/addonfactory-ucc-generator.git",
"directory": "ui"
},
"version": "5.56.0",
"license": "Apache-2.0",
"private": true,
"author": "Splunk Inc.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should not? I want to be consistent with other packages (sui)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with leaving it, jsut not sure what it actually means, should it be splunk supported (as ucc is not) etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what "Splunk Supported" means

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but probably not every Splunk authored code is splunk supported ™

"homepage": "https://splunk.github.io/addonfactory-ucc-generator",
"scripts": {
"build": "cross-env NODE_ENV=production webpack --bail",
"build:lib": "cross-env NODE_ENV=production tsc --project tsconfig.lib.json",
"build:watch": "webpack --watch",
"format": "prettier \"src/**/*.(js|jsx|ts|tsx|css)\" --write",
"format:verify": "prettier \"src/**/*.(js|jsx|ts|tsx|css)\" --list-different",
Expand All @@ -18,7 +26,8 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test-storybook": "test-storybook",
"test-storybook:update-snapshots": "yarn run test-storybook -u"
"test-storybook:update-snapshots": "yarn run test-storybook -u",
"prepublishOnly": "yarn run build:lib"
},
"dependencies": {
"@splunk/dashboard-action-buttons": "^27.5.1",
Expand Down Expand Up @@ -119,6 +128,10 @@
"webpack-dev-server": "^5.2.0",
"webpack-merge": "^6.0.1"
},
"peerDependencies": {
"react": "^16.14.0",
"typescript": "^5.6.3"
},
"resolutions": {
"@npmcli/git": "^2.1.0",
"@types/react": "^16.14.62",
Expand All @@ -139,5 +152,9 @@
"workerDirectory": [
"src/public"
]
}
},
"types": "./dist/lib/publicApi.d.ts",
"files": [
"dist/lib"
]
}
12 changes: 6 additions & 6 deletions ui/src/components/BaseFormView/BaseFormView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ import {
UtilControlWrapper,
ServiceGroup,
OauthConfiguration,
CustomHook,
AnyEntity,
OAuthEntity,
BasicEntity,
ChangeRecord,
CustomHookClass,
EntitiesAllowingModifications,
} from '../../types/components/BaseFormTypes';
import {
Expand All @@ -51,6 +49,7 @@ import {
} from '../FormModifications/FormModifications';
import { GlobalConfig } from '../../types/globalConfig/globalConfig';
import { shouldHideForPlatform } from '../../util/pageContext';
import { CustomHookConstructor, CustomHookInstance } from '../../types/components/CustomHookClass';

function onCustomHookError(params: { methodName: string; error?: CustomHookError }) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -107,7 +106,7 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {

datadict: Record<string, AcceptableFormValueOrNullish>;

hook?: CustomHook;
hook?: CustomHookInstance;

// eslint-disable-next-line camelcase
state_enabled?: boolean;
Expand Down Expand Up @@ -1072,7 +1071,7 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {
if (type === 'external') {
import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then(
(external) => {
const Hook = external.default;
const Hook = external.default as CustomHookConstructor;
this.hook = new Hook(
globalConfig,
this.props.serviceName,
Expand All @@ -1088,13 +1087,14 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {
// @ts-expect-error should be exported to other js module and imported here
__non_webpack_require__(
[`app/${this.appName}/js/build/custom/${module}`],
(Hook: CustomHookClass) => {
(Hook: CustomHookConstructor) => {
this.hook = new Hook(
globalConfig,
this.props.serviceName,
this.state,
this.props.mode,
this.util
this.util,
this.props.groupName
);
resolve(Hook);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import Switch from '@splunk/react-ui/Switch';
import { isFalse } from '../../util/considerFalseAndTruthy';

interface CheckBoxComponentProps {
export interface CheckBoxComponentProps {
value: 0 | 1 | boolean;
handleChange: (field: string, value: 0 | 1) => void;
field: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Base: Story = {
utilCustomFunctions: {
setState: () => {},
setErrorFieldMsg: () => {},
clearAllErrorMsg: () => {},
clearAllErrorMsg: (state) => state,
setErrorMsg: () => {},
},
handleChange: () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const renderControlWrapper = (props: Partial<ControlWrapperProps>) => {
utilCustomFunctions: {
setState: () => {},
setErrorFieldMsg: () => {},
clearAllErrorMsg: () => {},
clearAllErrorMsg: (state) => state,
setErrorMsg: () => {},
},
handleChange: () => {},
Expand Down
54 changes: 20 additions & 34 deletions ui/src/components/CustomControl/CustomControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,12 @@ import { getUnifiedConfigs } from '../../util/util';
import { getBuildDirPath } from '../../util/script';
import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes';
import { UtilBaseForm } from '../../types/components/BaseFormTypes';
import { GlobalConfig } from '../../types/globalConfig/globalConfig';
import { Mode } from '../../constants/modes';
import { invariant } from '../../util/invariant';
import { CustomControlConstructor } from './CustomControlBase';
import { ControlData } from './CustomControl.types';

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

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

interface ICustomCompProps {
data: IData;
interface Props {
data: ControlData;
field: string;
handleChange: (field: string, newValue: AcceptableFormValueOrNullish) => void;
controlOptions: { src: string; type: string };
Expand All @@ -38,37 +20,40 @@ interface ICustomCompProps {
utilCustomFunctions: UtilBaseForm;
}

interface ICustomCompState {
interface State {
loading: boolean;
}

class CustomControl extends React.Component<ICustomCompProps, ICustomCompState> {
class CustomControl extends React.Component<Props, State> {
static loadCustomControl = (
module: string,
type: string,
appName: string
): Promise<ICustomCompClass> =>
): Promise<CustomControlConstructor> =>
new Promise((resolve) => {
if (type === 'external') {
import(/* webpackIgnore: true */ `${getBuildDirPath()}/custom/${module}.js`).then(
(external) => {
const Control = external.default;
async (external) => {
const Control = external.default as CustomControlConstructor;
resolve(Control);
}
);
} 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);
});
__non_webpack_require__(
[`app/${appName}/js/build/custom/${module}`],
(Control: CustomControlConstructor) => {
resolve(Control);
}
);
}
});

shouldRender: boolean;

el?: HTMLElement;

constructor(props: ICustomCompProps) {
constructor(props: Props) {
super(props);
this.state = {
loading: true,
Expand All @@ -85,14 +70,15 @@ class CustomControl extends React.Component<ICustomCompProps, ICustomCompState>
this.props.controlOptions.type,
appName
).then((Control) => {
invariant(this.el !== undefined, 'Element should be defined');
const customControl = new Control(
globalConfig,
this.el,
this.props.data,
this.setValue,
this.props.utilCustomFunctions
);
customControl?.render();
customControl.render();
soleksy-splunk marked this conversation as resolved.
Show resolved Hide resolved

if (typeof customControl.validation === 'function') {
this.props.addCustomValidator(this.props.field, customControl.validation);
Expand All @@ -101,7 +87,7 @@ class CustomControl extends React.Component<ICustomCompProps, ICustomCompState>
});
}

shouldComponentUpdate(_nextProps: ICustomCompProps, nextState: ICustomCompState) {
shouldComponentUpdate(_nextProps: Props, nextState: State) {
if (!nextState.loading && this.shouldRender) {
this.shouldRender = false;
return true;
Expand Down
8 changes: 8 additions & 0 deletions ui/src/components/CustomControl/CustomControl.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes';
import { Mode } from '../../constants/modes';

export interface ControlData {
value: AcceptableFormValueOrNullish;
mode: Mode;
serviceName: string;
}
43 changes: 43 additions & 0 deletions ui/src/components/CustomControl/CustomControlBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { GlobalConfig } from '../../types/globalConfig/globalConfig';
import { UtilBaseForm } from '../../types/components/BaseFormTypes';
import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes';
import { ControlData } from './CustomControl.types';

type ValueSetter = (newValue: AcceptableFormValueOrNullish) => void;

export type CustomControlInstance<T extends typeof CustomControlBase = typeof CustomControlBase> =
InstanceType<T>;

export type CustomControlConstructor<
T extends typeof CustomControlBase = typeof CustomControlBase
> = new (...args: ConstructorParameters<T>) => CustomControlInstance<T>;

export abstract class CustomControlBase {
protected globalConfig: GlobalConfig;

protected el: HTMLElement;

protected data: ControlData;

protected setValue: ValueSetter;

protected util: UtilBaseForm;

constructor(
globalConfig: GlobalConfig,
el: HTMLElement,
data: ControlData,
setValue: ValueSetter,
util: UtilBaseForm
) {
this.globalConfig = globalConfig;
this.el = el;
this.data = data;
this.setValue = setValue;
this.util = util;
}

abstract render(): void;

validation?(field: string, value: ControlData['value']): string | undefined;
}
16 changes: 4 additions & 12 deletions ui/src/components/CustomTab/CustomTab.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
import React, { useEffect, useRef, useState } from 'react';
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';

type Tab = z.infer<typeof TabSchema>;
import { CustomTabConstructor } from './CustomTabBase';
import { Tab } from './CustomTab.types';

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<HTMLDivElement>(null);

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

const loadCustomTab = (): Promise<ICustomTabClass> =>
const loadCustomTab = (): Promise<CustomTabConstructor> =>
new Promise((resolve) => {
if (tab.customTab?.type === 'external') {
import(
Expand All @@ -37,7 +29,7 @@ const CustomTab: React.FC<CustomTabProps> = ({ tab }) => {
// @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: ICustomTabClass) => resolve(Control)
(Control: CustomTabConstructor) => resolve(Control)
);
}
});
Expand Down
4 changes: 4 additions & 0 deletions ui/src/components/CustomTab/CustomTab.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { z } from 'zod';
import { TabSchema } from '../../types/globalConfig/pages';

export type Tab = z.infer<typeof TabSchema>;
21 changes: 21 additions & 0 deletions ui/src/components/CustomTab/CustomTabBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Tab } from './CustomTab.types';

export type CustomTabInstance<T extends typeof CustomTabBase = typeof CustomTabBase> =
InstanceType<T>;

export type CustomTabConstructor<T extends typeof CustomTabBase = typeof CustomTabBase> = new (
...args: ConstructorParameters<T>
) => CustomTabInstance<T>;

export abstract class CustomTabBase {
protected tab: Tab;

protected el: HTMLDivElement;

constructor(tab: Tab, el: HTMLDivElement) {
this.tab = tab;
this.el = el;
}

abstract render(): void;
}
2 changes: 1 addition & 1 deletion ui/src/components/DeleteModal/DeleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const ModalWrapper = styled(Modal)`
width: 800px;
`;

interface DeleteModalProps {
export interface DeleteModalProps {
page: StandardPages;
handleRequestClose: () => void;
serviceName: string;
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/EntityPage/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { StandardPages } from '../../types/components/shareableTypes';
import PageContext from '../../context/PageContext';
import { UCCButton } from '../UCCButton/UCCButton';

interface EntityPageProps {
export interface EntityPageProps {
handleRequestClose: () => void;
serviceName: string;
mode: Mode;
Expand Down
Loading
Loading