1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +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
name="apps"
/>
 
test-app
</react-mdl-CardTitle>
<react-mdl-CardText>
@ -41,27 +41,48 @@ exports[`renders correctly with permissions 1`] = `
/>
</a>
</react-mdl-CardMenu>
<hr />
<react-mdl-Tabs
activeTab={0}
className="mdl-color--grey-100"
onChange={[Function]}
ripple={true}
tabBarProps={
Object {
"style": Object {
"width": "100%",
},
<div>
<react-mdl-CardActions
border={true}
style={
Object {
"alignItems": "center",
"display": "flex",
"justifyContent": "space-between",
}
}
}
>
<react-mdl-Tab>
Details
</react-mdl-Tab>
<react-mdl-Tab>
Edit
</react-mdl-Tab>
</react-mdl-Tabs>
>
<span />
<react-mdl-Button
accent={true}
onClick={[Function]}
title="Delete application"
>
Delete
</react-mdl-Button>
</react-mdl-CardActions>
<hr />
<react-mdl-Tabs
activeTab={0}
className="mdl-color--grey-100"
onChange={[Function]}
ripple={true}
tabBarProps={
Object {
"style": Object {
"width": "100%",
},
}
}
>
<react-mdl-Tab>
Details
</react-mdl-Tab>
<react-mdl-Tab>
Edit
</react-mdl-Tab>
</react-mdl-Tabs>
</div>
<react-mdl-Grid
style={
Object {
@ -214,7 +235,7 @@ exports[`renders correctly without permission 1`] = `
<react-mdl-Icon
name="apps"
/>
 
test-app
</react-mdl-CardTitle>
<react-mdl-CardText>
@ -232,7 +253,6 @@ exports[`renders correctly without permission 1`] = `
/>
</a>
</react-mdl-CardMenu>
<hr />
<react-mdl-Grid
style={

View File

@ -4,9 +4,11 @@ import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import {
Button,
Grid,
Cell,
Card,
CardActions,
CardTitle,
CardText,
CardMenu,
@ -64,7 +66,9 @@ class ClientApplications extends PureComponent {
application: PropTypes.object,
location: PropTypes.object,
storeApplicationMetaData: PropTypes.func.isRequired,
deleteApplication: PropTypes.func.isRequired,
hasPermission: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
constructor(props) {
@ -78,6 +82,14 @@ class ClientApplications extends PureComponent {
formatFullDateTime(v) {
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() {
if (!this.props.application) {
return <ProgressBar indeterminate />;
@ -173,9 +185,6 @@ class ClientApplications extends PureComponent {
</Grid>
) : (
<Grid>
<Cell col={12}>
<h5>Edit app meta data</h5>
</Cell>
<Cell col={6} tablet={12}>
<StatefulTextfield
value={url}
@ -194,7 +203,7 @@ class ClientApplications extends PureComponent {
<Cell col={6} tablet={12}>
<MySelect
label="Icon"
options={icons.map(v => ({ name: v, label: v }))}
options={icons.map(v => ({ key: v, label: v }))}
value={icon}
onChange={e => storeApplicationMetaData(appName, 'icon', e.target.value)}
filled
@ -211,7 +220,7 @@ class ClientApplications extends PureComponent {
return (
<Card shadow={0} className={commonStyles.fullwidth}>
<CardTitle style={{ paddingTop: '24px', paddingRight: '64px', wordBreak: 'break-all' }}>
<Icon name={icon} />
<Icon name={icon || 'apps'} />
&nbsp;{appName}
</CardTitle>
{description && <CardText>{description}</CardText>}
@ -220,18 +229,33 @@ class ClientApplications extends PureComponent {
<IconLink url={url} icon="link" />
</CardMenu>
)}
<hr />
{hasPermission(UPDATE_APPLICATION) ? (
<Tabs
activeTab={this.state.activeTab}
onChange={tabId => this.setState({ activeTab: tabId })}
ripple
tabBarProps={{ style: { width: '100%' } }}
className="mdl-color--grey-100"
>
<Tab>Details</Tab>
<Tab>Edit</Tab>
</Tabs>
<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
activeTab={this.state.activeTab}
onChange={tabId => this.setState({ activeTab: tabId })}
ripple
tabBarProps={{ style: { width: '100%' } }}
className="mdl-color--grey-100"
>
<Tab>Details</Tab>
<Tab>Edit</Tab>
</Tabs>
</div>
) : (
''
)}

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
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';
const mapStateToProps = (state, props) => {
@ -19,6 +19,7 @@ const mapStateToProps = (state, props) => {
const Constainer = connect(mapStateToProps, {
fetchApplication,
storeApplicationMetaData,
deleteApplication,
})(ApplicationEdit);
export default Constainer;

View File

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

View File

@ -2,10 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
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 = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
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 UPDATE_APPLICATION_FIELD = 'UPDATE_APPLICATION_FIELD';
export const DELETE_APPLICATION = 'DELETE_APPLICATION';
export const ERROR_DELETE_APPLICATION = 'ERROR_DELETE_APPLICATION';
const recieveAllApplications = json => ({
type: RECEIVE_ALL_APPLICATIONS,
@ -41,3 +43,11 @@ export function fetchApplication(appName) {
.then(json => dispatch(recieveApplication(json)))
.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 { 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';
function getInitState() {
@ -14,6 +14,11 @@ const store = (state = getInitState(), action) => {
return state.set('list', new List(action.value.applications));
case UPDATE_APPLICATION_FIELD:
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_LOGIN:
return getInitState();