font-weight: 600;
}
+.underline {
+ text-decoration: underline;
+}
+
mark {
background: none;
color: var(--baseFontColor);
onSelectRepository: (repository: AzureRepository) => void;
project: AzureProject;
repositories?: AzureRepository[];
+ searchQuery?: string;
selectedRepository?: AzureRepository;
startsOpen: boolean;
}
const PAGE_SIZE = 30;
+function highlight(text: string, term?: string, underline = false) {
+ if (!term || !text.toLowerCase().includes(term.toLowerCase())) {
+ return text;
+ }
+
+ // Capture only the first occurence by using a capturing group to get
+ // everything after the first occurence
+ const [pre, found, post] = text.split(new RegExp(`(${term})(.*)`, 'i'));
+ return (
+ <>
+ {pre}
+ <strong className={classNames({ underline })}>{found}</strong>
+ {post}
+ </>
+ );
+}
+
export default function AzureProjectAccordion(props: AzureProjectAccordionProps) {
- const { importing, loading, startsOpen, project, repositories = [], selectedRepository } = props;
+ const {
+ importing,
+ loading,
+ startsOpen,
+ project,
+ repositories = [],
+ searchQuery,
+ selectedRepository
+ } = props;
const [open, setOpen] = React.useState(startsOpen);
const handleClick = () => {
})}
onClick={handleClick}
open={open}
- title={<h3 title={project.description}>{project.name}</h3>}>
+ title={<h3 title={project.description}>{highlight(project.name, searchQuery, true)}</h3>}>
{open && (
<DeferredSpinner loading={loading}>
{/* The extra loading guard is to prevent the flash of the Alert */}
<div className="display-flex-wrap">
{limitedRepositories.map(repo => (
<div
- className="display-flex-start spacer-right spacer-bottom create-project-azdo-repo"
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
key={repo.name}>
{repo.sqProjectKey ? (
<>
<CheckIcon className="spacer-right" fill={colors.green} size={14} />
<div className="overflow-hidden">
<div className="little-spacer-bottom text-ellipsis">
- <strong title={repo.sqProjectName}>
- <Link to={getProjectUrl(repo.sqProjectKey)}>
- {repo.sqProjectName}
- </Link>
- </strong>
+ <Link to={getProjectUrl(repo.sqProjectKey)} title={repo.sqProjectName}>
+ {highlight(repo.sqProjectName || repo.name, searchQuery)}
+ </Link>
</div>
<em>{translate('onboarding.create_project.repository_imported')}</em>
</div>
disabled={importing}
onCheck={() => props.onSelectRepository(repo)}
value={repo.name}>
- <strong className="text-ellipsis" title={repo.name}>
- {repo.name}
- </strong>
+ <span className="text-ellipsis" title={repo.name}>
+ {highlight(repo.name, searchQuery)}
+ </span>
</Radio>
)}
</div>
repositories: T.Dict<AzureRepository[]>;
searching?: boolean;
searchResults?: T.Dict<AzureRepository[]>;
+ searchQuery?: string;
selectedRepository?: AzureRepository;
settings?: AlmSettingsInstance;
submittingToken?: boolean;
}
if (searchQuery.length === 0) {
- this.setState({ searchResults: undefined });
+ this.setState({ searchResults: undefined, searchQuery: undefined });
return;
}
.catch(() => []);
if (this.mounted) {
- this.setState({ searching: false, searchResults: groupBy(results, 'projectName') });
+ this.setState({
+ searching: false,
+ searchResults: groupBy(results, 'projectName'),
+ searchQuery
+ });
}
};
repositories,
searching,
searchResults,
+ searchQuery,
selectedRepository,
settings,
submittingToken,
repositories={repositories}
searching={searching}
searchResults={searchResults}
+ searchQuery={searchQuery}
selectedRepository={selectedRepository}
settings={settings}
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)}
repositories: T.Dict<AzureRepository[]>;
searching?: boolean;
searchResults?: T.Dict<AzureRepository[]>;
+ searchQuery?: string;
selectedRepository?: AzureRepository;
settings?: AlmSettingsInstance;
showPersonalAccessTokenForm?: boolean;
repositories,
searching,
searchResults,
+ searchQuery,
selectedRepository,
settings,
showPersonalAccessTokenForm,
{loading && <i className="spinner" />}
- {!loading && !settings && (
+ {!loading && !(settings && settings.url) && (
<WrongBindingCountAlert alm={AlmKeys.Azure} canAdmin={!!canAdmin} />
)}
<div className="huge-spacer-bottom">
<SearchBox
onChange={props.onSearch}
- placeholder={translate('onboarding.create_project.search_repositories_by_name')}
+ placeholder={translate('onboarding.create_project.search_projects_repositories')}
/>
</div>
<DeferredSpinner loading={Boolean(searching)}>
projects={projects}
repositories={repositories}
searchResults={searchResults}
+ searchQuery={searchQuery}
selectedRepository={selectedRepository}
/>
</DeferredSpinner>
projects?: AzureProject[];
repositories: T.Dict<AzureRepository[]>;
searchResults?: T.Dict<AzureRepository[]>;
+ searchQuery?: string;
selectedRepository?: AzureRepository;
}
projects = [],
repositories,
searchResults,
+ searchQuery,
selectedRepository
} = props;
project={p}
repositories={searchResults ? searchResults[p.name] : repositories[p.name]}
selectedRepository={selectedRepository}
+ searchQuery={searchQuery}
startsOpen={searchResults !== undefined || i === 0}
/>
))}
expect(shallowRender({ importing: true, repositories: [mockAzureRepository()] })).toMatchSnapshot(
'importing'
);
+ expect(
+ shallowRender({
+ repositories: [
+ mockAzureRepository({ name: 'this repo is the best' }),
+ mockAzureRepository({
+ name: 'This is a repo with class',
+ sqProjectKey: 'sq-key',
+ sqProjectName: 'SQ Name'
+ })
+ ],
+ searchQuery: 'repo'
+ })
+ ).toMatchSnapshot('search results');
});
it('should open when clicked', () => {
await waitAndUpdate(wrapper);
expect(wrapper.state().searching).toBe(false);
expect(wrapper.state().searchResults).toEqual({ [repositories[0].projectName]: repositories });
+ expect(wrapper.state().searchQuery).toBe(query);
// Ignore opening a project when search results are displayed
(getAzureRepositories as jest.Mock).mockClear();
wrapper.instance().handleSearchRepositories('');
expect(searchAzureRepositories).not.toBeCalled();
expect(wrapper.state().searchResults).toBeUndefined();
+ expect(wrapper.state().searchQuery).toBeUndefined();
});
it('should select and import a repository', async () => {
className="display-flex-wrap"
>
<div
- className="display-flex-start spacer-right spacer-bottom create-project-azdo-repo"
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
key="Azure repo 1"
>
<Radio
onCheck={[Function]}
value="Azure repo 1"
>
- <strong
+ <span
className="text-ellipsis"
title="Azure repo 1"
>
Azure repo 1
- </strong>
+ </span>
</Radio>
</div>
</div>
</BoxedGroupAccordion>
`;
+exports[`should render correctly: search results 1`] = `
+<BoxedGroupAccordion
+ className="big-spacer-bottom open"
+ onClick={[Function]}
+ open={true}
+ title={
+ <h3
+ title="Azure Project"
+ >
+ azure-project-1
+ </h3>
+ }
+>
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="display-flex-wrap"
+ >
+ <div
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
+ key="this repo is the best"
+ >
+ <Radio
+ checked={false}
+ className="overflow-hidden"
+ disabled={false}
+ onCheck={[Function]}
+ value="this repo is the best"
+ >
+ <span
+ className="text-ellipsis"
+ title="this repo is the best"
+ >
+ this
+ <strong
+ className=""
+ >
+ repo
+ </strong>
+ is the best
+ </span>
+ </Radio>
+ </div>
+ <div
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
+ key="This is a repo with class"
+ >
+ <CheckIcon
+ className="spacer-right"
+ fill="#00aa00"
+ size={14}
+ />
+ <div
+ className="overflow-hidden"
+ >
+ <div
+ className="little-spacer-bottom text-ellipsis"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ title="SQ Name"
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": undefined,
+ "id": "sq-key",
+ },
+ }
+ }
+ >
+ SQ Name
+ </Link>
+ </div>
+ <em>
+ onboarding.create_project.repository_imported
+ </em>
+ </div>
+ </div>
+ </div>
+ <ListFooter
+ count={2}
+ loadMore={[Function]}
+ total={2}
+ />
+ </DeferredSpinner>
+</BoxedGroupAccordion>
+`;
+
exports[`should render correctly: with repositories 1`] = `
<BoxedGroupAccordion
className="big-spacer-bottom open"
className="display-flex-wrap"
>
<div
- className="display-flex-start spacer-right spacer-bottom create-project-azdo-repo"
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
key="Azure repo 1"
>
<Radio
onCheck={[Function]}
value="Azure repo 1"
>
- <strong
+ <span
className="text-ellipsis"
title="Azure repo 1"
>
Azure repo 1
- </strong>
+ </span>
</Radio>
</div>
<div
- className="display-flex-start spacer-right spacer-bottom create-project-azdo-repo"
+ className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
key="Azure repo 1"
>
<CheckIcon
<div
className="little-spacer-bottom text-ellipsis"
>
- <strong
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
title="SQ Name"
- >
- <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "sq-key",
- },
- }
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": undefined,
+ "id": "sq-key",
+ },
}
- >
- SQ Name
- </Link>
- </strong>
+ }
+ >
+ SQ Name
+ </Link>
</div>
<em>
onboarding.create_project.repository_imported
</span>
}
/>
+ <WrongBindingCountAlert
+ alm="azure"
+ canAdmin={true}
+ />
<div
className="huge-spacer-bottom"
>
<SearchBox
onChange={[MockFunction]}
- placeholder="onboarding.create_project.search_repositories_by_name"
+ placeholder="onboarding.create_project.search_projects_repositories"
/>
</div>
<DeferredSpinner
</span>
}
/>
+ <WrongBindingCountAlert
+ alm="azure"
+ canAdmin={true}
+ />
<div
className="display-flex-justify-center"
>
}
.create-project-azdo-repo {
- width: 250px;
+ width: 410px;
min-height: 40px;
+ box-sizing: border-box;
+ margin-right: auto;
}
.create-project-import-bbs .open .boxed-group-header {
onboarding.create_project.repository_imported=Already set up
onboarding.create_project.see_project=See the project
onboarding.create_project.search_repositories_by_name=Search for repository name starting with...
+onboarding.create_project.search_projects_repositories=Search for projects and repositories
onboarding.create_project.search_repositories=Search for a repository
onboarding.create_project.select_repositories=Select repositories
onboarding.create_project.select_all_repositories=Select all available repositories