import Projects from './Projects';
import CreateProjectForm from './CreateProjectForm';
import ListFooter from '../../components/controls/ListFooter';
-import { PAGE_SIZE, Type, Project } from './utils';
+import { PAGE_SIZE, Project } from './utils';
import { getComponents, getProvisioned } from '../../api/components';
import { Organization } from '../../app/types';
import { translate } from '../../helpers/l10n';
createProjectForm: boolean;
page: number;
projects: Project[];
+ provisioned: boolean;
qualifiers: string;
query: string;
ready: boolean;
selection: string[];
total: number;
- type: Type;
}
export default class App extends React.PureComponent<Props, State> {
createProjectForm: false,
ready: false,
projects: [],
+ provisioned: false,
total: 0,
page: 1,
query: '',
qualifiers: 'TRK',
- type: Type.All,
selection: []
};
this.requestProjects = debounce(this.requestProjects, 250);
q: this.state.query ? this.state.query : undefined
});
- requestProjects = () => {
- switch (this.state.type) {
- case Type.All:
- this.requestAllProjects();
- break;
- case Type.Provisioned:
- this.requestProvisioned();
- break;
- }
- };
+ requestProjects = () =>
+ this.state.provisioned ? this.requestProvisioned() : this.requestAllProjects();
requestProvisioned = () => {
const data = this.getFilters();
this.setState({ ready: false, page: 1, query, selection: [] }, this.requestProjects);
};
- onTypeChanged = (newType: Type) => {
+ onProvisionedChanged = (provisioned: boolean) => {
this.setState(
- { ready: false, page: 1, query: '', type: newType, qualifiers: 'TRK', selection: [] },
+ { ready: false, page: 1, query: '', provisioned, qualifiers: 'TRK', selection: [] },
this.requestProjects
);
};
onQualifierChanged = (newQualifier: string) => {
this.setState(
- { ready: false, page: 1, query: '', type: Type.All, qualifiers: newQualifier, selection: [] },
+ {
+ ready: false,
+ page: 1,
+ provisioned: false,
+ query: '',
+ qualifiers: newQualifier,
+ selection: []
+ },
this.requestProjects
);
};
/>
<Search
- {...this.props}
- {...this.state}
onAllSelected={this.onAllSelected}
onAllDeselected={this.onAllDeselected}
onDeleteProjects={this.requestProjects}
+ onProvisionedChanged={this.onProvisionedChanged}
onQualifierChanged={this.onQualifierChanged}
onSearch={this.onSearch}
- onTypeChanged={this.onTypeChanged}
+ organization={this.props.organization}
+ projects={this.state.projects}
+ provisioned={this.state.provisioned}
+ qualifiers={this.state.qualifiers}
+ query={this.state.query}
+ ready={this.state.ready}
+ selection={this.state.selection}
+ topLevelQualifiers={this.props.topLevelQualifiers}
+ total={this.state.total}
/>
<Projects
import * as React from 'react';
import Modal from 'react-modal';
import * as Select from 'react-select';
-import { Type } from './utils';
import {
getPermissionTemplates,
PermissionTemplate,
export interface Props {
onClose: () => void;
organization: string;
+ provisioned: boolean;
qualifier: string;
query: string;
selection: string[];
total: number;
- type: Type;
}
interface State {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as Select from 'react-select';
import { sortBy } from 'lodash';
import BulkApplyTemplateModal from './BulkApplyTemplateModal';
import DeleteModal from './DeleteModal';
-import { Type, QUALIFIERS_ORDER } from './utils';
+import { QUALIFIERS_ORDER } from './utils';
import { Project } from './utils';
import { Organization } from '../../app/types';
-import RadioToggle from '../../components/controls/RadioToggle';
import Checkbox from '../../components/controls/Checkbox';
import { translate } from '../../helpers/l10n';
+import QualifierIcon from '../../components/shared/QualifierIcon';
+import Tooltip from '../../components/controls/Tooltip';
export interface Props {
onAllDeselected: () => void;
onAllSelected: () => void;
onDeleteProjects: () => void;
+ onProvisionedChanged: (provisioned: boolean) => void;
onQualifierChanged: (qualifier: string) => void;
onSearch: (query: string) => void;
- onTypeChanged: (type: Type) => void;
organization: Organization;
projects: Project[];
+ provisioned: boolean;
qualifiers: string;
query: string;
ready: boolean;
selection: any[];
topLevelQualifiers: string[];
total: number;
- type: Type;
}
interface State {
this.props.onSearch(q);
};
- getTypeOptions = () => [
- { value: Type.All, label: 'All' },
- { value: Type.Provisioned, label: 'Provisioned' }
- ];
-
getQualifierOptions = () => {
- const options = this.props.topLevelQualifiers.map(q => {
- return { value: q, label: translate('qualifiers', q) };
- });
+ const options = this.props.topLevelQualifiers.map(q => ({
+ label: translate('qualifiers', q),
+ value: q
+ }));
return sortBy(options, option => QUALIFIERS_ORDER.indexOf(option.value));
};
this.setState({ bulkApplyTemplateModal: false });
};
+ handleQualifierChange = ({ value }: { value: string }) => this.props.onQualifierChanged(value);
+
renderCheckbox = () => {
const isAllChecked =
this.props.projects.length > 0 && this.props.selection.length === this.props.projects.length;
this.props.selection.length > 0 &&
this.props.selection.length < this.props.projects.length;
const checked = isAllChecked || thirdState;
- return <Checkbox checked={checked} thirdState={thirdState} onCheck={this.onCheck} />;
+ return (
+ <Checkbox
+ checked={checked}
+ id="projects-selection"
+ thirdState={thirdState}
+ onCheck={this.onCheck}
+ />
+ );
};
+ renderQualifierOption = (option: { label: string; value: string }) =>
+ <span>
+ <QualifierIcon className="little-spacer-right" qualifier={option.value} />
+ {option.label}
+ </span>;
+
renderQualifierFilter = () => {
const options = this.getQualifierOptions();
if (options.length < 2) {
}
return (
<td className="thin nowrap text-middle">
- <RadioToggle
+ <Select
+ className="input-medium"
+ clearable={false}
+ disabled={!this.props.ready}
+ optionRenderer={this.renderQualifierOption}
options={this.getQualifierOptions()}
value={this.props.qualifiers}
+ valueRenderer={this.renderQualifierOption}
name="projects-qualifier"
- onCheck={this.props.onQualifierChanged}
+ onChange={this.handleQualifierChange}
+ searchable={false}
/>
</td>
);
};
+ renderTypeFilter = () =>
+ this.props.qualifiers === 'TRK'
+ ? <td className="thin nowrap text-middle">
+ <Checkbox
+ className="link-checkbox-control"
+ checked={this.props.provisioned}
+ id="projects-provisioned"
+ onCheck={this.props.onProvisionedChanged}>
+ <span className="little-spacer-left">
+ {translate('provisioning.only_provisioned')}
+ <Tooltip overlay={translate('provisioning.only_provisioned.tooltip')}>
+ <i className="spacer-left icon-help" />
+ </Tooltip>
+ </span>
+ </Checkbox>
+ </td>
+ : null;
+
render() {
const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
return (
{this.props.ready ? this.renderCheckbox() : <i className="spinner" />}
</td>
{this.renderQualifierFilter()}
- <td className="thin nowrap text-middle">
- <RadioToggle
- options={this.getTypeOptions()}
- value={this.props.type}
- name="projects-type"
- onCheck={this.props.onTypeChanged}
- />
- </td>
+ {this.renderTypeFilter()}
<td className="text-middle">
<form onSubmit={this.onSubmit} className="search-box">
<button className="search-box-submit button-clean">
<BulkApplyTemplateModal
onClose={this.closeBulkApplyTemplateModal}
organization={this.props.organization.key}
+ provisioned={this.props.provisioned}
qualifier={this.props.qualifiers}
query={this.props.query}
selection={this.props.selection}
total={this.props.total}
- type={this.props.type}
/>}
{this.state.deleteModal &&
return lodash;
});
+// actual version breaks `mount`
+jest.mock('rc-tooltip', () => ({
+ default: function Tooltip() {
+ return null;
+ }
+}));
+
jest.mock('../../../api/components', () => ({
getComponents: jest.fn(),
getProvisioned: jest.fn(() => Promise.resolve({ paging: { total: 0 }, projects: [] }))
import * as React from 'react';
import { mount } from 'enzyme';
import App, { Props } from '../App';
-import { Type } from '../utils';
const getComponents = require('../../../api/components').getComponents as jest.Mock<any>;
const getProvisioned = require('../../../api/components').getProvisioned as jest.Mock<any>;
expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'TRK' });
});
-it('changes type', () => {
+it('selects provisioned', () => {
const wrapper = mountRender();
- wrapper.find('Search').prop<Function>('onTypeChanged')(Type.Provisioned);
+ wrapper.find('Search').prop<Function>('onProvisionedChanged')(true);
expect(getProvisioned).lastCalledWith(defaultSearchParameters);
});
-it('changes qualifier and resets type', () => {
+it('changes qualifier and resets provisioned', () => {
const wrapper = mountRender();
- wrapper.setState({ type: Type.Provisioned });
+ wrapper.setState({ provisioned: true });
wrapper.find('Search').prop<Function>('onQualifierChanged')('VW');
expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'VW' });
});
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
-import { Type } from '../utils';
import { click } from '../../../helpers/testUtils';
const applyTemplateToProject = require('../../../api/permissions')
<BulkApplyTemplateModal
onClose={jest.fn()}
organization="org"
+ provisioned={true}
qualifier="TRK"
query="bla"
selection={[]}
total={17}
- type={Type.All}
{...props}
/>
);
import * as React from 'react';
import { shallow } from 'enzyme';
import Search, { Props } from '../Search';
-import { Type } from '../utils';
import { change, click } from '../../../helpers/testUtils';
const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
it('updates qualifier', () => {
const onQualifierChanged = jest.fn();
const wrapper = shallowRender({ onQualifierChanged, topLevelQualifiers: ['TRK', 'VW', 'APP'] });
- wrapper.find('RadioToggle[name="projects-qualifier"]').prop<Function>('onCheck')('VW');
+ wrapper.find('Select[name="projects-qualifier"]').prop<Function>('onChange')({ value: 'VW' });
expect(onQualifierChanged).toBeCalledWith('VW');
});
-it('updates type', () => {
- const onTypeChanged = jest.fn();
- const wrapper = shallowRender({ onTypeChanged });
- wrapper.find('RadioToggle[name="projects-type"]').prop<Function>('onCheck')(Type.Provisioned);
- expect(onTypeChanged).toBeCalledWith(Type.Provisioned);
+it('selects provisioned', () => {
+ const onProvisionedChanged = jest.fn();
+ const wrapper = shallowRender({ onProvisionedChanged });
+ wrapper.find('Checkbox[id="projects-provisioned"]').prop<Function>('onCheck')(true);
+ expect(onProvisionedChanged).toBeCalledWith(true);
+});
+
+it('does not render provisioned filter for portfolios', () => {
+ const wrapper = shallowRender();
+ expect(wrapper.find('Checkbox[id="projects-provisioned"]').exists()).toBeTruthy();
+ wrapper.setProps({ qualifiers: 'VW' });
+ expect(wrapper.find('Checkbox[id="projects-provisioned"]').exists()).toBeFalsy();
});
it('searches', () => {
const onAllSelected = jest.fn();
const wrapper = shallowRender({ onAllDeselected, onAllSelected });
- wrapper.find('Checkbox').prop<Function>('onCheck')(true);
+ wrapper.find('Checkbox[id="projects-selection"]').prop<Function>('onCheck')(true);
expect(onAllSelected).toBeCalled();
- wrapper.find('Checkbox').prop<Function>('onCheck')(false);
+ wrapper.find('Checkbox[id="projects-selection"]').prop<Function>('onCheck')(false);
expect(onAllDeselected).toBeCalled();
});
onAllDeselected={jest.fn()}
onAllSelected={jest.fn()}
onDeleteProjects={jest.fn()}
+ onProvisionedChanged={jest.fn()}
onQualifierChanged={jest.fn()}
onSearch={jest.fn()}
- onTypeChanged={jest.fn()}
organization={organization}
projects={[]}
+ provisioned={false}
qualifiers="TRK"
query=""
ready={true}
selection={[]}
topLevelQualifiers={['TRK']}
total={0}
- type={Type.All}
{...props}
/>
);
<BulkApplyTemplateModal
onClose={[Function]}
organization="org"
+ provisioned={false}
qualifier="TRK"
query=""
selection={Array []}
total={0}
- type="ALL"
/>
`;
>
<Checkbox
checked={false}
+ id="projects-selection"
onCheck={[Function]}
thirdState={false}
/>
<td
className="thin nowrap text-middle"
>
- <RadioToggle
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ className="input-medium"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
name="projects-qualifier"
- onCheck={[Function]}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
value="TRK"
+ valueComponent={[Function]}
+ valueKey="value"
+ valueRenderer={[Function]}
/>
</td>
<td
className="thin nowrap text-middle"
>
- <RadioToggle
- disabled={false}
- name="projects-type"
+ <Checkbox
+ checked={false}
+ className="link-checkbox-control"
+ id="projects-provisioned"
onCheck={[Function]}
- options={
- Array [
- Object {
- "label": "All",
- "value": "ALL",
- },
- Object {
- "label": "Provisioned",
- "value": "PROVISIONED",
- },
- ]
- }
- value="ALL"
- />
+ thirdState={false}
+ >
+ <span
+ className="little-spacer-left"
+ >
+ provisioning.only_provisioned
+ <Tooltip
+ overlay="provisioning.only_provisioned.tooltip"
+ placement="bottom"
+ >
+ <i
+ className="spacer-left icon-help"
+ />
+ </Tooltip>
+ </span>
+ </Checkbox>
</td>
<td
className="text-middle"
>
<Checkbox
checked={false}
+ id="projects-selection"
onCheck={[Function]}
thirdState={false}
/>
<td
className="thin nowrap text-middle"
>
- <RadioToggle
- disabled={false}
- name="projects-type"
+ <Checkbox
+ checked={false}
+ className="link-checkbox-control"
+ id="projects-provisioned"
onCheck={[Function]}
- options={
- Array [
- Object {
- "label": "All",
- "value": "ALL",
- },
- Object {
- "label": "Provisioned",
- "value": "PROVISIONED",
- },
- ]
- }
- value="ALL"
- />
+ thirdState={false}
+ >
+ <span
+ className="little-spacer-left"
+ >
+ provisioning.only_provisioned
+ <Tooltip
+ overlay="provisioning.only_provisioned.tooltip"
+ placement="bottom"
+ >
+ <i
+ className="spacer-left icon-help"
+ />
+ </Tooltip>
+ </span>
+ </Checkbox>
</td>
<td
className="text-middle"
*/
export const PAGE_SIZE = 50;
-export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP', 'DEV'];
-
-export enum Type {
- All = 'ALL',
- Provisioned = 'PROVISIONED'
-}
+export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
export interface Project {
key: string;
.Select-value svg,
.Select-value [class^="icon-"] {
- padding-top: 4px;
+ padding-top: 5px;
}
.Select-value img {
}
}
+.link-checkbox-control {
+ display: inline-block;
+ padding: 4px 0 5px;
+ line-height: 16px;
+}
+
a.active-link,
.link-active {
.link-no-underline;
provisioning.no_analysis=No analysis has been performed since creation. The only available section is the configuration.
provisioning.no_analysis.delete=Either you should retry to analyze the project, or simply {0}.
provisioning.no_analysis.delete_it=delete it
+provisioning.only_provisioned=Only Provisioned
+provisioning.only_provisioned.tooltip=Provisioned projects are projects that have been created, but have not been analyzed yet.
#------------------------------------------------------------------------------