[MM-63534] Show errors from Support Packet generation in System Console (#30535)

Этот коммит содержится в:
Ben Schumacher
2025-03-27 12:47:19 +01:00
коммит произвёл GitHub
родитель 4118a0f612
Коммит 8ce78c302d
4 изменённых файлов: 126 добавлений и 27 удалений

Просмотреть файл

@@ -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
className="CommercialSupportModal__packet_contents_download"
>
@@ -86,6 +70,26 @@ exports[`components/CommercialSupportModal should match snapshot 1`] = `
/>
</strong>
</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
className="CommercialSupportModal__download"
>

Просмотреть файл

@@ -24,3 +24,13 @@
font-family: Metropolis, sans-serif;
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 React from 'react';
import {Client4} from 'mattermost-redux/client';
import CommercialSupportModal from 'components/commercial_support_modal/commercial_support_modal';
import {TestHelper} from 'utils/test_helper';
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', () => {
const mockUser = TestHelper.getUserMock();
const wrapper = shallow(
<CommercialSupportModal
onExited={jest.fn()}
showBannerWarning={true}
isCloud={false}
currentUser={mockUser}
packetContents={[]}
/>,
);
const wrapper = shallow(<CommercialSupportModal {...baseProps}/>);
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;
packetContents: SupportPacketContent[];
loading: boolean;
error?: string;
};
export default class CommercialSupportModal extends React.PureComponent<Props, State> {
@@ -106,11 +107,18 @@ export default class CommercialSupportModal extends React.PureComponent<Props, S
};
downloadSupportPacket = async () => {
this.setState({loading: true});
this.setState({loading: true, error: undefined});
const res = await fetch(this.genereateDownloadURLWithParams(), {
method: 'GET',
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();
this.setState({loading: false});
@@ -214,6 +222,11 @@ export default class CommercialSupportModal extends React.PureComponent<Props, S
</div>
))}
<div className='CommercialSupportModal__download'>
{this.state.error && (
<div className='CommercialSupportModal__error'>
<span className='error-text'>{this.state.error}</span>
</div>
)}
<a
className='btn btn-primary DownloadSupportPacket'
onClick={this.downloadSupportPacket}