소스 검색

SONARCLOUD-275 Add better scroll and repositories search on project import

tags/7.6
Grégoire Aubert 5 년 전
부모
커밋
14205b07e4

+ 48
- 19
server/sonar-web/src/main/js/apps/create/project/RemoteRepositories.tsx 파일 보기

@@ -22,11 +22,12 @@ import * as classNames from 'classnames';
import AlmRepositoryItem from './AlmRepositoryItem';
import SetupProjectBox from './SetupProjectBox';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import SearchBox from '../../../components/controls/SearchBox';
import UpgradeOrganizationBox from '../components/UpgradeOrganizationBox';
import { Alert } from '../../../components/ui/Alert';
import { getRepositories } from '../../../api/alm-integration';
import { isDefined } from '../../../helpers/types';
import { translateWithParameters } from '../../../helpers/l10n';
import { Alert } from '../../../components/ui/Alert';
import { translateWithParameters, translate } from '../../../helpers/l10n';

interface Props {
almApplication: T.AlmApplication;
@@ -41,6 +42,7 @@ interface State {
highlight: boolean;
loading: boolean;
repositories: T.AlmRepository[];
search: string;
selectedRepositories: SelectedRepositories;
successfullyUpgraded: boolean;
}
@@ -51,6 +53,7 @@ export default class RemoteRepositories extends React.PureComponent<Props, State
highlight: false,
loading: true,
repositories: [],
search: '',
selectedRepositories: {},
successfullyUpgraded: false
};
@@ -116,6 +119,10 @@ export default class RemoteRepositories extends React.PureComponent<Props, State
});
};

handleSearch = (search: string) => {
this.setState({ search });
};

toggleRepository = (repository: T.AlmRepository) => {
this.setState(({ selectedRepositories }) => ({
selectedRepositories: {
@@ -128,16 +135,29 @@ export default class RemoteRepositories extends React.PureComponent<Props, State
};

render() {
const { highlight, loading, repositories, selectedRepositories } = this.state;
const { highlight, loading, repositories, search, selectedRepositories } = this.state;
const { almApplication, organization } = this.props;
const isPaidOrg = organization.subscription === 'PAID';
const hasPrivateRepositories = repositories.some(repository => Boolean(repository.private));
const showSearchBox = repositories.length > 5;
const showUpgradebox =
!isPaidOrg && hasPrivateRepositories && organization.actions && organization.actions.admin;

const filteredRepositories = repositories.filter(
repo => !search || repo.label.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="create-project">
<div className="flex-1 huge-spacer-right">
{showSearchBox && (
<div className="spacer-bottom">
<SearchBox
minLength={1}
onChange={this.handleSearch}
placeholder={translate('search.search_for_repositories')}
value={this.state.search}
/>
</div>
)}
{this.state.successfullyUpgraded && (
<Alert variant="success">
{translateWithParameters(
@@ -148,7 +168,14 @@ export default class RemoteRepositories extends React.PureComponent<Props, State
)}
<DeferredSpinner loading={loading}>
<ul>
{repositories.map(repo => (
{filteredRepositories.length === 0 && (
<li className="big-spacer-top note">
{showUpgradebox
? translateWithParameters('no_results_for_x', search)
: translate('onboarding.create_project.no_repositories')}
</li>
)}
{filteredRepositories.map(repo => (
<AlmRepositoryItem
disabled={Boolean(repo.private && !isPaidOrg)}
highlightUpgradeBox={this.handleHighlightUpgradeBox}
@@ -163,22 +190,24 @@ export default class RemoteRepositories extends React.PureComponent<Props, State
</DeferredSpinner>
</div>
{organization && (
<div className="huge-spacer-left">
<SetupProjectBox
onProjectCreate={this.props.onProjectCreate}
onProvisionFail={this.handleProvisionFail}
organization={organization}
selectedRepositories={Object.keys(selectedRepositories)
.map(r => selectedRepositories[r])
.filter(isDefined)}
/>
{showUpgradebox && (
<UpgradeOrganizationBox
className={classNames({ highlight })}
onOrganizationUpgrade={this.handleOrganizationUpgrade}
<div className={classNames({ 'create-project-side-with-search': showSearchBox })}>
<div className="create-project-side-sticky">
<SetupProjectBox
onProjectCreate={this.props.onProjectCreate}
onProvisionFail={this.handleProvisionFail}
organization={organization}
selectedRepositories={Object.keys(selectedRepositories)
.map(r => selectedRepositories[r])
.filter(isDefined)}
/>
)}
{showUpgradebox && (
<UpgradeOrganizationBox
className={classNames({ highlight })}
onOrganizationUpgrade={this.handleOrganizationUpgrade}
organization={organization}
/>
)}
</div>
</div>
)}
</div>

+ 15
- 0
server/sonar-web/src/main/js/apps/create/project/__tests__/RemoteRepositories-test.tsx 파일 보기

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { times } from 'lodash';
import { shallow } from 'enzyme';
import RemoteRepositories from '../RemoteRepositories';
import { getRepositories } from '../../../../api/alm-integration';
@@ -92,6 +93,20 @@ it('should not display the organization upgrade box', () => {
expect(wrapper.find('UpgradeOrganizationBox').exists()).toBe(false);
});

it('should display a search box to filter repositories', async () => {
(getRepositories as jest.Mock<any>).mockResolvedValueOnce({
repositories: times(6, i => ({ label: `Project ${i}`, installationKey: `key-${i}` }))
});

const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(wrapper.find('SearchBox').exists()).toBe(true);
expect(wrapper.find('AlmRepositoryItem')).toHaveLength(6);
wrapper.find('SearchBox').prop<Function>('onChange')('3');
expect(wrapper.find('AlmRepositoryItem')).toHaveLength(1);
});

function shallowRender(props: Partial<RemoteRepositories['props']> = {}) {
return shallow(
<RemoteRepositories

+ 47
- 33
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/RemoteRepositories-test.tsx.snap 파일 보기

@@ -11,28 +11,38 @@ exports[`should display the list of repositories 1`] = `
loading={true}
timeout={100}
>
<ul />
<ul>
<li
className="big-spacer-top note"
>
onboarding.create_project.no_repositories
</li>
</ul>
</DeferredSpinner>
</div>
<div
className="huge-spacer-left"
className=""
>
<SetupProjectBox
onProjectCreate={[MockFunction]}
onProvisionFail={[Function]}
organization={
Object {
"alm": Object {
"key": "github",
"url": "",
},
"key": "sonarsource",
"name": "SonarSource",
"subscription": "FREE",
<div
className="create-project-side-sticky"
>
<SetupProjectBox
onProjectCreate={[MockFunction]}
onProvisionFail={[Function]}
organization={
Object {
"alm": Object {
"key": "github",
"url": "",
},
"key": "sonarsource",
"name": "SonarSource",
"subscription": "FREE",
}
}
}
selectedRepositories={Array []}
/>
selectedRepositories={Array []}
/>
</div>
</div>
</div>
`;
@@ -99,24 +109,28 @@ exports[`should display the list of repositories 2`] = `
</DeferredSpinner>
</div>
<div
className="huge-spacer-left"
className=""
>
<SetupProjectBox
onProjectCreate={[MockFunction]}
onProvisionFail={[Function]}
organization={
Object {
"alm": Object {
"key": "github",
"url": "",
},
"key": "sonarsource",
"name": "SonarSource",
"subscription": "FREE",
<div
className="create-project-side-sticky"
>
<SetupProjectBox
onProjectCreate={[MockFunction]}
onProvisionFail={[Function]}
organization={
Object {
"alm": Object {
"key": "github",
"url": "",
},
"key": "sonarsource",
"name": "SonarSource",
"subscription": "FREE",
}
}
}
selectedRepositories={Array []}
/>
selectedRepositories={Array []}
/>
</div>
</div>
</div>
`;

+ 9
- 0
server/sonar-web/src/main/js/apps/create/project/style.css 파일 보기

@@ -57,6 +57,15 @@
box-shadow: none;
}

.create-project-side-with-search {
margin-top: calc(4 * var(--gridSize));
}

.create-project-side-sticky {
position: sticky;
top: 68px;
}

.create-project-setup {
display: flex;
overflow: hidden;

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties 파일 보기

@@ -939,6 +939,7 @@ search.search_by_login_or_name=Search by login or name...
search.search_by_name=Search by name...
search.search_by_name_or_key=Search by name or key...
search.search_for_tags=Search for tags...
search.search_for_repositories=Search for repositories...
search.search_for_rules=Search for rules...
search.search_for_languages=Search for languages...
search.search_for_cwe=Search for CWEs...
@@ -2759,6 +2760,7 @@ onboarding.create_project.1_repository_created_as_public=1 repository will be cr
onboarding.create_project.x_repository_created_as_public={0} repositories will be created as public projects on SonarCloud
onboarding.create_project.1_repository_created_as_private=1 repository will be created as a private project on SonarCloud
onboarding.create_project.x_repository_created_as_private={0} repositories will be created as private projects on SonarCloud
onboarding.create_project.no_repositories=No repositories found for this organization.
onboarding.create_project.organization=Organization
onboarding.create_project.project_key=Project key
onboarding.create_project.project_key.description=Up to 400 characters. All letters, digits, dash, underscore, point or colon.

Loading…
취소
저장