mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
fix: use the first project if there's no default project (#859)
* refactor: extract FeatureToggleListCreate component * fix: use the first project if there's no default project
This commit is contained in:
parent
f33ca9db4b
commit
fb8d0e7efc
@ -0,0 +1,57 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button, IconButton, Tooltip } from '@material-ui/core';
|
||||||
|
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||||
|
import { Add } from '@material-ui/icons';
|
||||||
|
import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds';
|
||||||
|
import { IFeaturesFilter } from 'hooks/useFeaturesFilter';
|
||||||
|
import { useCreateFeaturePath } from 'component/feature/CreateFeatureButton/useCreateFeaturePath';
|
||||||
|
|
||||||
|
interface ICreateFeatureButtonProps {
|
||||||
|
loading: boolean;
|
||||||
|
filter: IFeaturesFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateFeatureButton = ({
|
||||||
|
loading,
|
||||||
|
filter,
|
||||||
|
}: ICreateFeatureButtonProps) => {
|
||||||
|
const smallScreen = useMediaQuery('(max-width:800px)');
|
||||||
|
const createFeature = useCreateFeaturePath(filter);
|
||||||
|
|
||||||
|
if (!createFeature) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={smallScreen}
|
||||||
|
show={
|
||||||
|
<Tooltip title="Create feature toggle">
|
||||||
|
<IconButton
|
||||||
|
component={Link}
|
||||||
|
to={createFeature.path}
|
||||||
|
data-test={NAVIGATE_TO_CREATE_FEATURE}
|
||||||
|
disabled={!createFeature.access}
|
||||||
|
>
|
||||||
|
<Add titleAccess="New" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<Button
|
||||||
|
to={createFeature.path}
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
component={Link}
|
||||||
|
data-test={NAVIGATE_TO_CREATE_FEATURE}
|
||||||
|
disabled={!createFeature.access}
|
||||||
|
className={classnames({ skeleton: loading })}
|
||||||
|
>
|
||||||
|
New feature toggle
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import { useDefaultProjectId } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||||
|
import { IFeaturesFilter } from 'hooks/useFeaturesFilter';
|
||||||
|
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
interface IUseCreateFeaturePathOutput {
|
||||||
|
path: string;
|
||||||
|
access: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateFeaturePath = (
|
||||||
|
filter: IFeaturesFilter
|
||||||
|
): IUseCreateFeaturePathOutput | undefined => {
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const defaultProjectId = useDefaultProjectId();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
|
const selectedProjectId =
|
||||||
|
filter.project === '*' || !filter.project
|
||||||
|
? defaultProjectId
|
||||||
|
: filter.project;
|
||||||
|
|
||||||
|
if (!selectedProjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: getCreateTogglePath(selectedProjectId, uiConfig.flags.E),
|
||||||
|
access: hasAccess(CREATE_FEATURE, selectedProjectId),
|
||||||
|
};
|
||||||
|
};
|
@ -2,9 +2,8 @@ import { useContext } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, IconButton, List, ListItem, Tooltip } from '@material-ui/core';
|
import { List, ListItem } from '@material-ui/core';
|
||||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||||
import { Add } from '@material-ui/icons';
|
|
||||||
import FeatureToggleListItem from './FeatureToggleListItem';
|
import FeatureToggleListItem from './FeatureToggleListItem';
|
||||||
import { SearchField } from 'component/common/SearchField/SearchField';
|
import { SearchField } from 'component/common/SearchField/SearchField';
|
||||||
import FeatureToggleListActions from './FeatureToggleListActions';
|
import FeatureToggleListActions from './FeatureToggleListActions';
|
||||||
@ -12,13 +11,11 @@ import ConditionallyRender from 'component/common/ConditionallyRender/Conditiona
|
|||||||
import PageContent from 'component/common/PageContent/PageContent';
|
import PageContent from 'component/common/PageContent/PageContent';
|
||||||
import HeaderTitle from 'component/common/HeaderTitle';
|
import HeaderTitle from 'component/common/HeaderTitle';
|
||||||
import loadingFeatures from './loadingFeatures';
|
import loadingFeatures from './loadingFeatures';
|
||||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
import ListPlaceholder from 'component/common/ListPlaceholder/ListPlaceholder';
|
import ListPlaceholder from 'component/common/ListPlaceholder/ListPlaceholder';
|
||||||
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton';
|
||||||
import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds';
|
import { useCreateFeaturePath } from '../CreateFeatureButton/useCreateFeaturePath';
|
||||||
import { resolveFilteredProjectId } from 'hooks/useFeaturesFilter';
|
|
||||||
|
|
||||||
const FeatureToggleList = ({
|
const FeatureToggleList = ({
|
||||||
features,
|
features,
|
||||||
@ -32,6 +29,7 @@ const FeatureToggleList = ({
|
|||||||
setSort,
|
setSort,
|
||||||
}) => {
|
}) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const createFeature = useCreateFeaturePath(filter);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const smallScreen = useMediaQuery('(max-width:800px)');
|
const smallScreen = useMediaQuery('(max-width:800px)');
|
||||||
const mobileView = useMediaQuery('(max-width:600px)');
|
const mobileView = useMediaQuery('(max-width:600px)');
|
||||||
@ -41,9 +39,6 @@ const FeatureToggleList = ({
|
|||||||
setFilter(prev => ({ ...prev, query }));
|
setFilter(prev => ({ ...prev, query }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolvedProjectId = resolveFilteredProjectId(filter);
|
|
||||||
const createURL = getCreateTogglePath(resolvedProjectId, flags.E);
|
|
||||||
|
|
||||||
const renderFeatures = () => {
|
const renderFeatures = () => {
|
||||||
features.forEach(e => {
|
features.forEach(e => {
|
||||||
e.reviveName = e.name;
|
e.reviveName = e.name;
|
||||||
@ -83,12 +78,16 @@ const FeatureToggleList = ({
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(createFeature?.access)}
|
||||||
|
show={() => (
|
||||||
<ListPlaceholder
|
<ListPlaceholder
|
||||||
text="No features available. Get started by adding a
|
text="No features available. Get started by adding a new feature toggle."
|
||||||
new feature toggle."
|
link={createFeature.path}
|
||||||
link={createURL}
|
|
||||||
linkText="Add your first toggle"
|
linkText="Add your first toggle"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -143,49 +142,9 @@ const FeatureToggleList = ({
|
|||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!archive}
|
condition={!archive}
|
||||||
show={
|
show={
|
||||||
<ConditionallyRender
|
<CreateFeatureButton
|
||||||
condition={smallScreen}
|
filter={filter}
|
||||||
show={
|
loading={loading}
|
||||||
<Tooltip title="Create feature toggle">
|
|
||||||
<IconButton
|
|
||||||
component={Link}
|
|
||||||
to={createURL}
|
|
||||||
data-test={
|
|
||||||
NAVIGATE_TO_CREATE_FEATURE
|
|
||||||
}
|
|
||||||
disabled={
|
|
||||||
!hasAccess(
|
|
||||||
CREATE_FEATURE,
|
|
||||||
resolvedProjectId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Add />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Button
|
|
||||||
to={createURL}
|
|
||||||
color="primary"
|
|
||||||
variant="contained"
|
|
||||||
component={Link}
|
|
||||||
data-test={
|
|
||||||
NAVIGATE_TO_CREATE_FEATURE
|
|
||||||
}
|
|
||||||
disabled={
|
|
||||||
!hasAccess(
|
|
||||||
CREATE_FEATURE,
|
|
||||||
resolvedProjectId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className={classnames({
|
|
||||||
skeleton: loading,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
New feature toggle
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -142,32 +142,6 @@ exports[`renders correctly with one feature 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
aria-disabled={true}
|
|
||||||
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary Mui-disabled Mui-disabled"
|
|
||||||
data-test="NAVIGATE_TO_CREATE_FEATURE"
|
|
||||||
href="/projects/default/create-toggle"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onDragLeave={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
onMouseDown={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseUp={[Function]}
|
|
||||||
onTouchEnd={[Function]}
|
|
||||||
onTouchMove={[Function]}
|
|
||||||
onTouchStart={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="MuiButton-label"
|
|
||||||
>
|
|
||||||
New feature toggle
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -339,32 +313,6 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
aria-disabled={true}
|
|
||||||
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary Mui-disabled Mui-disabled"
|
|
||||||
data-test="NAVIGATE_TO_CREATE_FEATURE"
|
|
||||||
href="/projects/default/create-toggle"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onClick={[Function]}
|
|
||||||
onDragLeave={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
onMouseDown={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
onMouseUp={[Function]}
|
|
||||||
onTouchEnd={[Function]}
|
|
||||||
onTouchMove={[Function]}
|
|
||||||
onTouchStart={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="MuiButton-label"
|
|
||||||
>
|
|
||||||
New feature toggle
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
|
|
||||||
|
const DEFAULT_PROJECT_ID = 'default';
|
||||||
|
|
||||||
|
export const useDefaultProjectId = (): string | undefined => {
|
||||||
|
const { projects = [] } = useProjects();
|
||||||
|
|
||||||
|
const defaultProject = projects.find(project => {
|
||||||
|
return project.id === DEFAULT_PROJECT_ID;
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultProject?.id || projects[0]?.id;
|
||||||
|
};
|
@ -36,16 +36,6 @@ export const useFeaturesFilter = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the current project ID a project has been selected,
|
|
||||||
// or the 'default' project if showing all projects.
|
|
||||||
export const resolveFilteredProjectId = (filter: IFeaturesFilter): string => {
|
|
||||||
if (!filter.project || filter.project === '*') {
|
|
||||||
return 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter.project;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterFeatures = (
|
const filterFeatures = (
|
||||||
features: IFeatureToggle[],
|
features: IFeatureToggle[],
|
||||||
filter: IFeaturesFilter
|
filter: IFeaturesFilter
|
||||||
|
Loading…
Reference in New Issue
Block a user