SDA-4186 (Add support to update user POD settings) (#1904)

* SDA-4186 - Add support to update user POD settings

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>

* SDA-4186 - Add support to update user POD settings

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>

* SDA-4186 - Fix unit test

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>

* SDA-4186 - Add unit test

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>

* SDA-4186 - Change pod info to user config

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>

---------

Signed-off-by: Kiran Niranjan <kiran.niranjan@symphony.com>
This commit is contained in:
Kiran Niranjan 2023-07-25 13:36:42 +05:30 committed by GitHub
parent 8eae1f77a4
commit 2d75704d7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 151 additions and 13 deletions

View File

@ -31,6 +31,7 @@ describe('about app', () => {
httpParserVersion: '11.12',
swiftSearchVersion: '1.55.3-beta.1',
swiftSearchSupportedVersion: 'N/A',
updatedHostname: 'N/A',
};
const onLabelEvent = 'on';
const ipcSendEvent = 'send';
@ -76,4 +77,15 @@ describe('about app', () => {
};
expect(spyIpc).toBeCalledWith('symphony-api', expectedData);
});
it('should display input when triple clicked on pod', () => {
const wrapper = shallow(React.createElement(AboutApp));
ipcRenderer.send('about-app-data', aboutDataMock);
const pod = wrapper.find(`[data-testid="POD_INFO"]`);
pod.simulate('click', { detail: 1 });
pod.simulate('click', { detail: 2 });
pod.simulate('click', { detail: 3 });
const podInput = wrapper.find('.AboutApp-pod-input');
expect(podInput.exists()).toEqual(true);
});
});

View File

@ -1184,6 +1184,13 @@ export class WindowHandler {
this.aboutAppWindow = null;
}
};
const handleUserPodUpdate = async (_event, hostname) => {
logger.info('window-handler: user updated pod url', hostname);
const url = new URL(`https://${hostname}`).toString();
await config.updateUserConfig({ url });
app.relaunch();
app.exit();
};
await versionHandler.getClientVersion(true, this.url);
@ -1208,6 +1215,7 @@ export class WindowHandler {
});
ipcMain.once('close-about-app', closeAboutApp);
ipcMain.once('user-pod-updated', handleUserPodUpdate);
this.aboutAppWindow.webContents.once('did-finish-load', async () => {
let client = '';
@ -1216,7 +1224,7 @@ export class WindowHandler {
}
const ABOUT_SYMPHONY_NAMESPACE = 'AboutSymphony';
const versionLocalised = i18n.t('Version', ABOUT_SYMPHONY_NAMESPACE)();
const { hostname } = parse(this.url || this.globalConfig.url);
const { hostname } = parse(this.userConfig.url || this.globalConfig.url);
const userConfig = config.userConfig;
const globalConfig = config.globalConfig;
const cloudConfig = config.cloudConfig;

View File

@ -9,7 +9,8 @@
"Copy config to clipboard": "Copy config to clipboard",
"Close": "Close",
"Copyright": "Copyright",
"Desktop Application": "Desktop Application"
"Desktop Application": "Desktop Application",
"Save and Restart": "Save and Restart"
},
"Actual Size": "Actual Size",
"Always on Top": "Always on Top",

View File

@ -9,7 +9,8 @@
"Copy config to clipboard": "Copy config to clipboard",
"Close": "Close",
"Copyright": "Copyright",
"Desktop Application": "Desktop Application"
"Desktop Application": "Desktop Application",
"Save and Restart": "Save and Restart"
},
"Actual Size": "Actual Size",
"Always on Top": "Always on Top",

View File

@ -1,3 +1,4 @@
import classNames from 'classnames';
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { productName } from '../../../package.json';
@ -5,6 +6,9 @@ import { apiCmds, apiName } from '../../common/api-interface';
import { i18n } from '../../common/i18n-preload';
import * as CopyIcon from '../../renderer/assets/copy-icon.svg';
import * as SymphonyLogo from '../../renderer/assets/new-symphony-logo.svg';
const HOSTNAME_REGEX = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
interface IState {
userConfig: object;
globalConfig: object;
@ -32,6 +36,10 @@ interface IState {
swiftSearchVersion?: string;
swiftSearchSupportedVersion?: string;
client?: string;
updatedHostname: string;
isPodEditing: boolean;
isValidHostname: boolean;
didUpdateHostname: boolean;
}
const ABOUT_SYMPHONY_NAMESPACE = 'AboutSymphony';
@ -44,6 +52,9 @@ export default class AboutApp extends React.Component<{}, IState> {
private readonly eventHandlers = {
onCopy: () => this.copy(),
onClose: () => this.close(),
onPodClick: (event) => this.onPodClick(event),
onPodChange: (event) => this.handlePodChange(event),
onPodInputBlur: (event) => this.handlePodInputBlur(event),
};
private closeButtonRef: React.RefObject<HTMLButtonElement>;
@ -75,6 +86,10 @@ export default class AboutApp extends React.Component<{}, IState> {
httpParserVersion: '',
swiftSearchVersion: '',
swiftSearchSupportedVersion: '',
updatedHostname: '',
isPodEditing: false,
isValidHostname: true,
didUpdateHostname: false,
};
this.updateState = this.updateState.bind(this);
}
@ -91,6 +106,8 @@ export default class AboutApp extends React.Component<{}, IState> {
sdaVersion,
sdaBuildNumber,
client,
didUpdateHostname,
isValidHostname,
} = this.state;
const appName = productName || 'Symphony';
@ -122,6 +139,10 @@ export default class AboutApp extends React.Component<{}, IState> {
value: `${formattedSfeVersion} ${client}`,
},
];
const closeButtonText =
isValidHostname && didUpdateHostname
? i18n.t('Save and Restart', ABOUT_SYMPHONY_NAMESPACE)()
: i18n.t('Close', ABOUT_SYMPHONY_NAMESPACE)();
return (
<div className='AboutApp' lang={i18n.getLocale()}>
@ -162,11 +183,11 @@ export default class AboutApp extends React.Component<{}, IState> {
<button
className='AboutApp-close-button'
onClick={this.eventHandlers.onClose}
title={i18n.t('Close', ABOUT_SYMPHONY_NAMESPACE)()}
title={closeButtonText}
data-testid={'CLOSE_BUTTON'}
ref={this.closeButtonRef}
>
{i18n.t('Close', ABOUT_SYMPHONY_NAMESPACE)()}
{closeButtonText}
</button>
</div>
</div>
@ -199,7 +220,10 @@ export default class AboutApp extends React.Component<{}, IState> {
*/
public copy(): void {
const { clientVersion, ...rest } = this.state;
const data = { ...{ sbeVersion: clientVersion }, ...rest };
const { isPodEditing, isValidHostname, didUpdateHostname, ...data } = {
...{ sbeVersion: clientVersion },
...rest,
};
if (data) {
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.aboutAppClipBoardData,
@ -213,9 +237,53 @@ export default class AboutApp extends React.Component<{}, IState> {
* Close modal
*/
public close(): void {
const { isValidHostname, didUpdateHostname, hostname } = this.state;
ipcRenderer.send('close-about-app');
if (isValidHostname && didUpdateHostname) {
ipcRenderer.send('user-pod-updated', hostname);
}
}
/**
* Enables editing mode
*/
public onPodClick(e): void {
if (e.detail === 3) {
this.setState({
isPodEditing: true,
didUpdateHostname: true,
});
}
}
/**
* Updates state with new POD URL
* @param e
*/
public handlePodChange = (e) => {
const { value } = e.target;
this.setState({ updatedHostname: value });
};
/**
* Validates and sets new hostname
*/
public handlePodInputBlur = (_event) => {
const { updatedHostname, hostname } = this.state;
if (!HOSTNAME_REGEX.test(updatedHostname)) {
this.setState({
isPodEditing: false,
isValidHostname: false,
});
} else {
this.setState({
isPodEditing: false,
isValidHostname: true,
hostname: updatedHostname || hostname,
});
}
};
/**
* Sets the component state
*
@ -223,7 +291,8 @@ export default class AboutApp extends React.Component<{}, IState> {
* @param data {Object} { buildNumber, clientVersion, version }
*/
private updateState(_event, data): void {
this.setState(data as IState);
const updatedData = { ...data, updatedHostname: data.hostname };
this.setState(updatedData as IState);
}
/**
@ -234,14 +303,46 @@ export default class AboutApp extends React.Component<{}, IState> {
return (
<section>
<ul className='AboutApp-symphony-section'>
{symphonySectionItems.map((item, key) => (
<li key={key}>
<strong>{item.key}</strong>
<span>{item.value}</span>
</li>
))}
{symphonySectionItems.map((item, key) => {
if (item.key === 'POD:') {
return this.renderEditablePodElement(item, key);
}
return (
<li key={key}>
<strong>{item.key}</strong>
<span>{item.value}</span>
</li>
);
})}
</ul>
</section>
);
}
private renderEditablePodElement = (item, key) => {
const { isPodEditing, isValidHostname, updatedHostname } = this.state;
return (
<li key={key}>
<strong className={'AboutApp-pod'}>{item.key}</strong>
{isPodEditing ? (
<input
className={'AboutApp-pod-input'}
type='text'
value={updatedHostname}
onChange={this.handlePodChange}
onBlur={this.handlePodInputBlur}
autoFocus
/>
) : (
<span
data-testid={'POD_INFO'}
className={classNames({ 'invalid-pod': !isValidHostname })}
onClick={this.eventHandlers.onPodClick}
>
{updatedHostname}
</span>
)}
</li>
);
};
}

View File

@ -200,4 +200,19 @@ body {
line-height: 16px;
color: @graphite-20;
}
&-symphony-section > li > input {
display: flex;
margin: 0 6px;
color: @graphite-80;
}
&-symphony-section > li > .invalid-pod {
color: red;
}
&-pod {
align-items: center;
display: flex;
}
}