Skip to content

Commit

Permalink
feat: add the untrusted source modal
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
  • Loading branch information
akurinnoy committed Jul 24, 2024
1 parent a288cf3 commit 5d8bd0e
Show file tree
Hide file tree
Showing 12 changed files with 628 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ import { AppState } from '@/store';
import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder';
import { FactoryResolverStateResolver } from '@/store/FactoryResolver';

const mockGet = jest.fn().mockReturnValue('all');
const mockUpdate = jest.fn().mockReturnValue(undefined);
const mockRemove = jest.fn().mockReturnValue(undefined);
jest.mock('@/services/session-storage', () => {
return {
__esModule: true,
default: {
get: (...args: unknown[]) => mockGet(...args),
update: (...args: unknown[]) => mockUpdate(...args),
remove: (...args: unknown[]) => mockRemove(...args),
},
// enum
SessionStorageKey: {
TRUSTED_SOURCES: 'trusted-sources', // 'all' or 'repo1,repo2,...'
},
};
});
// mute the outputs
console.error = jest.fn();
console.warn = jest.fn();
Expand Down Expand Up @@ -202,6 +219,7 @@ describe('Workspace creation time', () => {
),
);

await waitFor(() => expect(mockPost).toHaveBeenCalledTimes(3));
await waitFor(
() =>
expect(mockPost.mock.calls).toEqual([
Expand All @@ -213,7 +231,6 @@ describe('Workspace creation time', () => {
]),
{ timeout: 1500 },
);
expect(mockPost).toHaveBeenCalledTimes(3);

await waitFor(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder';

const { createSnapshot, renderComponent } = getComponentRenderer(getComponent);

const mockGet = jest.fn();
jest.mock('@/services/session-storage', () => {
return {
__esModule: true,
default: {
get: (...args: unknown[]) => mockGet(...args),
},
// enum
SessionStorageKey: {
TRUSTED_SOURCES: 'trusted-sources', // 'all' or 'repo1,repo2,...'
},
};
});

const history = createMemoryHistory({
initialEntries: ['/'],
});
Expand All @@ -36,6 +50,7 @@ describe('GitRepoLocationInput', () => {
let store: Store;

beforeEach(() => {
mockGet.mockReturnValue('all');
store = new FakeStoreBuilder()
.withDwServerConfig({
defaults: {
Expand Down Expand Up @@ -75,6 +90,50 @@ describe('GitRepoLocationInput', () => {
expect(window.open).not.toHaveBeenCalled();
});

describe('trusted/untrusted source', () => {
jest.mock('@/components/UntrustedSourceModal');

test('untrusted source', () => {
mockGet.mockReturnValue('repo1,repo2');
renderComponent(store);

const input = screen.getByRole('textbox');
expect(input).toBeValid();

userEvent.paste(input, 'http://test-location');

expect(input).toHaveValue('http://test-location');

const button = screen.getByRole('button', { name: 'Create & Open' });
userEvent.click(button);

const untrustedSourceModal = screen.queryByRole('dialog', { name: /untrusted source/i });
expect(untrustedSourceModal).not.toBeNull();

expect(window.open).not.toHaveBeenCalled();
});

test('trusted source', () => {
mockGet.mockReturnValue('all');
renderComponent(store);

const input = screen.getByRole('textbox');
expect(input).toBeValid();

userEvent.paste(input, 'http://test-location');

expect(input).toHaveValue('http://test-location');

const button = screen.getByRole('button', { name: 'Create & Open' });
userEvent.click(button);

const untrustedSourceModal = screen.queryByRole('dialog', { name: /untrusted source/i });
expect(untrustedSourceModal).toBeNull();

expect(window.open).toHaveBeenCalledTimes(1);
});
});

describe('valid HTTP location', () => {
describe('factory URL w/o other parameters', () => {
test('trim spaces from the input value', () => {
Expand Down
57 changes: 44 additions & 13 deletions packages/dashboard-frontend/src/components/ImportFromGit/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable simple-import-sort/imports */
/*
* Copyright (c) 2018-2024 Red Hat, Inc.
* This program and the accompanying materials are made
Expand Down Expand Up @@ -33,14 +34,15 @@ import {
import { ExclamationCircleIcon } from '@patternfly/react-icons';
import { History } from 'history';
import React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { ConnectedProps, connect } from 'react-redux';

import { GitRepoOptions } from '@/components/ImportFromGit/GitRepoOptions';
import {
getGitRepoOptionsFromLocation,
setGitRepoOptionsToLocation,
validateLocation,
} from '@/components/ImportFromGit/helpers';
import { UntrustedSourceModal } from '@/components/UntrustedSourceModal';
import { GitRemote } from '@/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/getGitRemotes';
import { FactoryLocationAdapter } from '@/services/factory-location-adapter';
import { EDITOR_ATTR, EDITOR_IMAGE_ATTR } from '@/services/helpers/factoryFlow/buildFactoryParams';
Expand Down Expand Up @@ -70,6 +72,7 @@ export type State = {
devfilePath: string | undefined;
isFocused: boolean;
hasSupportedGitService: boolean;
isConfirmationOpen: boolean;
};

class ImportFromGit extends React.PureComponent<Props, State> {
Expand All @@ -87,6 +90,7 @@ class ImportFromGit extends React.PureComponent<Props, State> {
devfilePath: undefined,
isFocused: false,
hasSupportedGitService: false,
isConfirmationOpen: false,
};
}

Expand All @@ -100,7 +104,24 @@ class ImportFromGit extends React.PureComponent<Props, State> {
}
}

private openConfirmationDialog(): void {
this.setState({ isConfirmationOpen: true });
}

private handleConfirmationOnClose(): void {
this.setState({ isConfirmationOpen: false });
}

private handleConfirmationOnContinue(): void {
this.setState({ isConfirmationOpen: false });
this.startFactory();
}

private handleCreate(): void {
this.openConfirmationDialog();
}

private startFactory(): void {
const { editorDefinition, editorImage } = this.props;
const location = decodeURIComponent(this.state.location);

Expand Down Expand Up @@ -281,21 +302,31 @@ class ImportFromGit extends React.PureComponent<Props, State> {
}

public render() {
const { locationValidated } = this.state;
const { isConfirmationOpen, location, locationValidated } = this.state;
return (
<Panel>
<PanelHeader>
<Title headingLevel="h3">Import from Git</Title>
</PanelHeader>
<PanelMain>
<PanelMainBody>{this.buildForm()}</PanelMainBody>
</PanelMain>
{locationValidated === ValidatedOptions.success && (
<>
{isConfirmationOpen && (
<UntrustedSourceModal
location={location}
isOpen={isConfirmationOpen}
onContinue={() => this.handleConfirmationOnContinue()}
onClose={() => this.handleConfirmationOnClose()}
/>
)}
<Panel>
<PanelHeader>
<Title headingLevel="h3">Import from Git</Title>
</PanelHeader>
<PanelMain>
<PanelMainBody>{this.buildGitRepoOptions()}</PanelMainBody>
<PanelMainBody>{this.buildForm()}</PanelMainBody>
</PanelMain>
)}
</Panel>
{locationValidated === ValidatedOptions.success && (
<PanelMain>
<PanelMainBody>{this.buildGitRepoOptions()}</PanelMainBody>
</PanelMain>
)}
</Panel>
</>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2024 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import React from 'react';

import { Props } from '@/components/UntrustedSourceModal';

export class UntrustedSourceModal extends React.PureComponent<Props> {
render(): React.ReactNode {
const { isOpen, onContinue, onClose } = this.props;
if (isOpen === false) {
return null;
}

return (
<div>
<span>UntrustedSourceModal</span>
<button onClick={onContinue}>Continue</button>
{onClose === undefined ? null : <button onClick={onClose}>Cancel</button>}
</div>
);
}
}
Loading

0 comments on commit 5d8bd0e

Please sign in to comment.