1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

feat: Should be possible to remove applications

https://github.com/Unleash/unleash/issues/634
This commit is contained in:
Ivar Conradi Østhus 2020-09-23 23:18:53 +02:00
parent a097a90dbe
commit b1d30b045e
7 changed files with 112 additions and 42 deletions

View File

@ -23,7 +23,7 @@ exports[`renders correctly with permissions 1`] = `
<react-mdl-Icon <react-mdl-Icon
name="apps" name="apps"
/> />
 
test-app test-app
</react-mdl-CardTitle> </react-mdl-CardTitle>
<react-mdl-CardText> <react-mdl-CardText>
@ -41,6 +41,26 @@ exports[`renders correctly with permissions 1`] = `
/> />
</a> </a>
</react-mdl-CardMenu> </react-mdl-CardMenu>
<div>
<react-mdl-CardActions
border={true}
style={
Object {
"alignItems": "center",
"display": "flex",
"justifyContent": "space-between",
}
}
>
<span />
<react-mdl-Button
accent={true}
onClick={[Function]}
title="Delete application"
>
Delete
</react-mdl-Button>
</react-mdl-CardActions>
<hr /> <hr />
<react-mdl-Tabs <react-mdl-Tabs
activeTab={0} activeTab={0}
@ -62,6 +82,7 @@ exports[`renders correctly with permissions 1`] = `
Edit Edit
</react-mdl-Tab> </react-mdl-Tab>
</react-mdl-Tabs> </react-mdl-Tabs>
</div>
<react-mdl-Grid <react-mdl-Grid
style={ style={
Object { Object {
@ -214,7 +235,7 @@ exports[`renders correctly without permission 1`] = `
<react-mdl-Icon <react-mdl-Icon
name="apps" name="apps"
/> />
 
test-app test-app
</react-mdl-CardTitle> </react-mdl-CardTitle>
<react-mdl-CardText> <react-mdl-CardText>
@ -232,7 +253,6 @@ exports[`renders correctly without permission 1`] = `
/> />
</a> </a>
</react-mdl-CardMenu> </react-mdl-CardMenu>
<hr />
<react-mdl-Grid <react-mdl-Grid
style={ style={

View File

@ -4,9 +4,11 @@ import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
Button,
Grid, Grid,
Cell, Cell,
Card, Card,
CardActions,
CardTitle, CardTitle,
CardText, CardText,
CardMenu, CardMenu,
@ -64,7 +66,9 @@ class ClientApplications extends PureComponent {
application: PropTypes.object, application: PropTypes.object,
location: PropTypes.object, location: PropTypes.object,
storeApplicationMetaData: PropTypes.func.isRequired, storeApplicationMetaData: PropTypes.func.isRequired,
deleteApplication: PropTypes.func.isRequired,
hasPermission: PropTypes.func.isRequired, hasPermission: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
}; };
constructor(props) { constructor(props) {
@ -78,6 +82,14 @@ class ClientApplications extends PureComponent {
formatFullDateTime(v) { formatFullDateTime(v) {
return formatFullDateTimeWithLocale(v, this.props.location.locale); return formatFullDateTimeWithLocale(v, this.props.location.locale);
} }
deleteApplication = async evt => {
evt.preventDefault();
const { deleteApplication, appName } = this.props;
await deleteApplication(appName);
this.props.history.push('/applications');
};
render() { render() {
if (!this.props.application) { if (!this.props.application) {
return <ProgressBar indeterminate />; return <ProgressBar indeterminate />;
@ -173,9 +185,6 @@ class ClientApplications extends PureComponent {
</Grid> </Grid>
) : ( ) : (
<Grid> <Grid>
<Cell col={12}>
<h5>Edit app meta data</h5>
</Cell>
<Cell col={6} tablet={12}> <Cell col={6} tablet={12}>
<StatefulTextfield <StatefulTextfield
value={url} value={url}
@ -194,7 +203,7 @@ class ClientApplications extends PureComponent {
<Cell col={6} tablet={12}> <Cell col={6} tablet={12}>
<MySelect <MySelect
label="Icon" label="Icon"
options={icons.map(v => ({ name: v, label: v }))} options={icons.map(v => ({ key: v, label: v }))}
value={icon} value={icon}
onChange={e => storeApplicationMetaData(appName, 'icon', e.target.value)} onChange={e => storeApplicationMetaData(appName, 'icon', e.target.value)}
filled filled
@ -211,7 +220,7 @@ class ClientApplications extends PureComponent {
return ( return (
<Card shadow={0} className={commonStyles.fullwidth}> <Card shadow={0} className={commonStyles.fullwidth}>
<CardTitle style={{ paddingTop: '24px', paddingRight: '64px', wordBreak: 'break-all' }}> <CardTitle style={{ paddingTop: '24px', paddingRight: '64px', wordBreak: 'break-all' }}>
<Icon name={icon} /> <Icon name={icon || 'apps'} />
&nbsp;{appName} &nbsp;{appName}
</CardTitle> </CardTitle>
{description && <CardText>{description}</CardText>} {description && <CardText>{description}</CardText>}
@ -220,8 +229,22 @@ class ClientApplications extends PureComponent {
<IconLink url={url} icon="link" /> <IconLink url={url} icon="link" />
</CardMenu> </CardMenu>
)} )}
<hr />
{hasPermission(UPDATE_APPLICATION) ? ( {hasPermission(UPDATE_APPLICATION) ? (
<div>
<CardActions
border
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span />
<Button accent title="Delete application" onClick={this.deleteApplication}>
Delete
</Button>
</CardActions>
<hr />
<Tabs <Tabs
activeTab={this.state.activeTab} activeTab={this.state.activeTab}
onChange={tabId => this.setState({ activeTab: tabId })} onChange={tabId => this.setState({ activeTab: tabId })}
@ -232,6 +255,7 @@ class ClientApplications extends PureComponent {
<Tab>Details</Tab> <Tab>Details</Tab>
<Tab>Edit</Tab> <Tab>Edit</Tab>
</Tabs> </Tabs>
</div>
) : ( ) : (
'' ''
)} )}

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ApplicationEdit from './application-edit-component'; import ApplicationEdit from './application-edit-component';
import { fetchApplication, storeApplicationMetaData } from './../../store/application/actions'; import { fetchApplication, storeApplicationMetaData, deleteApplication } from './../../store/application/actions';
import { hasPermission } from '../../permissions'; import { hasPermission } from '../../permissions';
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
@ -19,6 +19,7 @@ const mapStateToProps = (state, props) => {
const Constainer = connect(mapStateToProps, { const Constainer = connect(mapStateToProps, {
fetchApplication, fetchApplication,
storeApplicationMetaData, storeApplicationMetaData,
deleteApplication,
})(ApplicationEdit); })(ApplicationEdit);
export default Constainer; export default Constainer;

View File

@ -34,9 +34,18 @@ function storeApplicationMetaData(appName, key, value) {
}).then(throwIfNotSuccess); }).then(throwIfNotSuccess);
} }
function deleteApplication(appName) {
return fetch(`${URI}/${appName}`, {
method: 'DELETE',
headers,
credentials: 'include',
}).then(throwIfNotSuccess);
}
export default { export default {
fetchApplication, fetchApplication,
fetchAll, fetchAll,
fetchApplicationsWithStrategyName, fetchApplicationsWithStrategyName,
storeApplicationMetaData, storeApplicationMetaData,
deleteApplication,
}; };

View File

@ -2,10 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ApplicationEditComponent from '../../component/application/application-edit-container'; import ApplicationEditComponent from '../../component/application/application-edit-container';
const render = ({ match: { params } }) => <ApplicationEditComponent appName={params.name} />; const render = ({ match: { params }, history }) => <ApplicationEditComponent appName={params.name} history={history} />;
render.propTypes = { render.propTypes = {
match: PropTypes.object.isRequired, match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
}; };
export default render; export default render;

View File

@ -7,6 +7,8 @@ export const ERROR_UPDATING_APPLICATION_DATA = 'ERROR_UPDATING_APPLICATION_DATA'
export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION'; export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION';
export const UPDATE_APPLICATION_FIELD = 'UPDATE_APPLICATION_FIELD'; export const UPDATE_APPLICATION_FIELD = 'UPDATE_APPLICATION_FIELD';
export const DELETE_APPLICATION = 'DELETE_APPLICATION';
export const ERROR_DELETE_APPLICATION = 'ERROR_DELETE_APPLICATION';
const recieveAllApplications = json => ({ const recieveAllApplications = json => ({
type: RECEIVE_ALL_APPLICATIONS, type: RECEIVE_ALL_APPLICATIONS,
@ -41,3 +43,11 @@ export function fetchApplication(appName) {
.then(json => dispatch(recieveApplication(json))) .then(json => dispatch(recieveApplication(json)))
.catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS));
} }
export function deleteApplication(appName) {
return dispatch =>
api
.deleteApplication(appName)
.then(() => dispatch({ type: DELETE_APPLICATION, appName }))
.catch(dispatchAndThrow(dispatch, ERROR_DELETE_APPLICATION));
}

View File

@ -1,5 +1,5 @@
import { fromJS, List, Map } from 'immutable'; import { fromJS, List, Map } from 'immutable';
import { RECEIVE_ALL_APPLICATIONS, RECEIVE_APPLICATION, UPDATE_APPLICATION_FIELD } from './actions'; import { RECEIVE_ALL_APPLICATIONS, RECEIVE_APPLICATION, UPDATE_APPLICATION_FIELD, DELETE_APPLICATION } from './actions';
import { USER_LOGOUT, USER_LOGIN } from '../user/actions'; import { USER_LOGOUT, USER_LOGIN } from '../user/actions';
function getInitState() { function getInitState() {
@ -14,6 +14,11 @@ const store = (state = getInitState(), action) => {
return state.set('list', new List(action.value.applications)); return state.set('list', new List(action.value.applications));
case UPDATE_APPLICATION_FIELD: case UPDATE_APPLICATION_FIELD:
return state.setIn(['apps', action.appName, action.key], action.value); return state.setIn(['apps', action.appName, action.key], action.value);
case DELETE_APPLICATION: {
const index = state.get('list').findIndex(item => item.appName === action.appName);
const result = state.removeIn(['list', index]);
return result.removeIn(['apps', action.appName]);
}
case USER_LOGOUT: case USER_LOGOUT:
case USER_LOGIN: case USER_LOGIN:
return getInitState(); return getInitState();