[MM-63534] Show errors from Support Packet generation in System Console (#30535)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4118a0f612
Коммит
8ce78c302d
@@ -60,22 +60,6 @@ exports[`components/CommercialSupportModal should match snapshot 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<AlertBanner
|
|
||||||
message={
|
|
||||||
<Memo(MemoizedFormattedMessage)
|
|
||||||
defaultMessage="Before downloading the Support Packet, set <strong>Output Logs to File</strong> to <strong>true</strong> and set <strong>File Log Level</strong> to <strong>DEBUG</strong> <debugLink>here</debugLink>."
|
|
||||||
id="commercial_support_modal.warning.banner"
|
|
||||||
values={
|
|
||||||
Object {
|
|
||||||
"debugLink": [Function],
|
|
||||||
"strong": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
mode="info"
|
|
||||||
onDismiss={[Function]}
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
className="CommercialSupportModal__packet_contents_download"
|
className="CommercialSupportModal__packet_contents_download"
|
||||||
>
|
>
|
||||||
@@ -86,6 +70,26 @@ exports[`components/CommercialSupportModal should match snapshot 1`] = `
|
|||||||
/>
|
/>
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="CommercialSupportModal__option"
|
||||||
|
key="basic.server.logs"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
className="CommercialSupportModal__options__checkbox"
|
||||||
|
disabled={true}
|
||||||
|
id="basic.server.logs"
|
||||||
|
name="basic.server.logs"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Server Logs"
|
||||||
|
id="mettormost.plugin.metrics.support.packet"
|
||||||
|
>
|
||||||
|
<Component />
|
||||||
|
</MemoizedFormattedMessage>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="CommercialSupportModal__download"
|
className="CommercialSupportModal__download"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -24,3 +24,13 @@
|
|||||||
font-family: Metropolis, sans-serif;
|
font-family: Metropolis, sans-serif;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CommercialSupportModal__error {
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--error-text);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,22 +4,94 @@
|
|||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import {Client4} from 'mattermost-redux/client';
|
||||||
|
|
||||||
import CommercialSupportModal from 'components/commercial_support_modal/commercial_support_modal';
|
import CommercialSupportModal from 'components/commercial_support_modal/commercial_support_modal';
|
||||||
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
describe('components/CommercialSupportModal', () => {
|
describe('components/CommercialSupportModal', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// Mock getSystemRoute to return a valid URL
|
||||||
|
jest.spyOn(Client4, 'getSystemRoute').mockImplementation(() => 'http://localhost:8065/api/v4/system');
|
||||||
|
|
||||||
|
// Mock createObjectURL
|
||||||
|
window.URL.createObjectURL = jest.fn().mockReturnValue('mock-url');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
|
||||||
|
// @ts-expect-error - TS doesn't like deleting built-in methods
|
||||||
|
delete window.URL.createObjectURL;
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseProps = {
|
||||||
|
onExited: jest.fn(),
|
||||||
|
showBannerWarning: false,
|
||||||
|
isCloud: false,
|
||||||
|
currentUser: TestHelper.getUserMock(),
|
||||||
|
packetContents: [
|
||||||
|
{id: 'basic.server.logs', label: 'Server Logs', selected: true, mandatory: true},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
test('should match snapshot', () => {
|
test('should match snapshot', () => {
|
||||||
const mockUser = TestHelper.getUserMock();
|
const wrapper = shallow(<CommercialSupportModal {...baseProps}/>);
|
||||||
const wrapper = shallow(
|
|
||||||
<CommercialSupportModal
|
|
||||||
onExited={jest.fn()}
|
|
||||||
showBannerWarning={true}
|
|
||||||
isCloud={false}
|
|
||||||
currentUser={mockUser}
|
|
||||||
packetContents={[]}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show error message when download fails', async () => {
|
||||||
|
const errorMessage = 'Failed to download';
|
||||||
|
const detailedError = 'Permission denied';
|
||||||
|
|
||||||
|
// Mock the fetch call to return an error
|
||||||
|
global.fetch = jest.fn().mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: false,
|
||||||
|
json: () => Promise.resolve({
|
||||||
|
message: errorMessage,
|
||||||
|
detailed_error: detailedError,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrapper = shallow<CommercialSupportModal>(<CommercialSupportModal {...baseProps}/>);
|
||||||
|
|
||||||
|
// Trigger download
|
||||||
|
const instance = wrapper.instance();
|
||||||
|
await instance.downloadSupportPacket();
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
// Verify error message is shown
|
||||||
|
const errorDiv = wrapper.find('.CommercialSupportModal__error');
|
||||||
|
expect(errorDiv.exists()).toBe(true);
|
||||||
|
expect(errorDiv.find('.error-text').text()).toBe(`${errorMessage}: ${detailedError}`);
|
||||||
|
|
||||||
|
// Verify loading state is reset
|
||||||
|
expect(wrapper.state('loading')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should clear error when starting new download', async () => {
|
||||||
|
// Mock the fetch call to succeed
|
||||||
|
global.fetch = jest.fn().mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
blob: () => Promise.resolve(new Blob()),
|
||||||
|
headers: {get: () => null},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrapper = shallow<CommercialSupportModal>(<CommercialSupportModal {...baseProps}/>);
|
||||||
|
|
||||||
|
// Set initial error state
|
||||||
|
wrapper.setState({error: 'Previous error'});
|
||||||
|
|
||||||
|
// Start download
|
||||||
|
const instance = wrapper.instance();
|
||||||
|
await instance.downloadSupportPacket();
|
||||||
|
|
||||||
|
// Verify error is cleared
|
||||||
|
expect(wrapper.state('error')).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type State = {
|
|||||||
showBannerWarning: boolean;
|
showBannerWarning: boolean;
|
||||||
packetContents: SupportPacketContent[];
|
packetContents: SupportPacketContent[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class CommercialSupportModal extends React.PureComponent<Props, State> {
|
export default class CommercialSupportModal extends React.PureComponent<Props, State> {
|
||||||
@@ -106,11 +107,18 @@ export default class CommercialSupportModal extends React.PureComponent<Props, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
downloadSupportPacket = async () => {
|
downloadSupportPacket = async () => {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true, error: undefined});
|
||||||
const res = await fetch(this.genereateDownloadURLWithParams(), {
|
const res = await fetch(this.genereateDownloadURLWithParams(), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {'Content-Type': 'application/zip'},
|
headers: {'Content-Type': 'application/zip'},
|
||||||
});
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
const error = data.message + ': ' + data.detailed_error;
|
||||||
|
this.setState({loading: false, error});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
|
|
||||||
@@ -214,6 +222,11 @@ export default class CommercialSupportModal extends React.PureComponent<Props, S
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className='CommercialSupportModal__download'>
|
<div className='CommercialSupportModal__download'>
|
||||||
|
{this.state.error && (
|
||||||
|
<div className='CommercialSupportModal__error'>
|
||||||
|
<span className='error-text'>{this.state.error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<a
|
<a
|
||||||
className='btn btn-primary DownloadSupportPacket'
|
className='btn btn-primary DownloadSupportPacket'
|
||||||
onClick={this.downloadSupportPacket}
|
onClick={this.downloadSupportPacket}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user