mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
redux: moved folders to it's own features folder
This commit is contained in:
97
public/app/features/folders/FolderPermissions.tsx
Normal file
97
public/app/features/folders/FolderPermissions.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { Component } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { connect } from 'react-redux';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import Permissions from 'app/core/components/Permissions/Permissions';
|
||||
import Tooltip from 'app/core/components/Tooltip/Tooltip';
|
||||
import PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo';
|
||||
import AddPermissions from 'app/core/components/Permissions/AddPermissions';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { NavModel, StoreState, FolderState } from 'app/types';
|
||||
import { getFolderByUid } from './state/actions';
|
||||
import { PermissionsStore } from 'app/stores/PermissionsStore/PermissionsStore';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
getFolderByUid: typeof getFolderByUid;
|
||||
folderUid: string;
|
||||
folder: FolderState;
|
||||
permissions: typeof PermissionsStore.Type;
|
||||
backendSrv: any;
|
||||
}
|
||||
|
||||
@inject('permissions')
|
||||
@observer
|
||||
export class FolderPermissions extends Component<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleAddPermission = this.handleAddPermission.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getFolderByUid(this.props.folderUid);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { permissions } = this.props;
|
||||
permissions.hideAddPermissions();
|
||||
}
|
||||
|
||||
handleAddPermission() {
|
||||
const { permissions } = this.props;
|
||||
permissions.toggleAddPermissions();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel, permissions, backendSrv, folder } = this.props;
|
||||
|
||||
if (folder.id === 0) {
|
||||
return <h2>Loading</h2>;
|
||||
}
|
||||
|
||||
const dashboardId = folder.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
<div className="page-container page-body">
|
||||
<div className="page-action-bar">
|
||||
<h3 className="page-sub-heading">Folder Permissions</h3>
|
||||
<Tooltip className="page-sub-heading-icon" placement="auto" content={PermissionsInfo}>
|
||||
<i className="gicon gicon-question gicon--has-hover" />
|
||||
</Tooltip>
|
||||
<div className="page-action-bar__spacer" />
|
||||
<button
|
||||
className="btn btn-success pull-right"
|
||||
onClick={this.handleAddPermission}
|
||||
disabled={permissions.isAddPermissionsVisible}
|
||||
>
|
||||
<i className="fa fa-plus" /> Add Permission
|
||||
</button>
|
||||
</div>
|
||||
<SlideDown in={permissions.isAddPermissionsVisible}>
|
||||
<AddPermissions permissions={permissions} />
|
||||
</SlideDown>
|
||||
<Permissions permissions={permissions} isFolder={true} dashboardId={dashboardId} backendSrv={backendSrv} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => {
|
||||
const uid = state.location.routeParams.uid;
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, `folder-permissions-${uid}`),
|
||||
folderUid: uid,
|
||||
folder: state.folder,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getFolderByUid,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(FolderPermissions));
|
||||
55
public/app/features/folders/FolderSettingsPage.test.tsx
Normal file
55
public/app/features/folders/FolderSettingsPage.test.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { FolderSettingsPage, Props } from './FolderSettingsPage';
|
||||
import { NavModel } from 'app/types';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
navModel: {} as NavModel,
|
||||
folderUid: '1234',
|
||||
folder: {
|
||||
id: 0,
|
||||
uid: '1234',
|
||||
title: 'loading',
|
||||
canSave: true,
|
||||
url: 'url',
|
||||
hasChanged: false,
|
||||
version: 1,
|
||||
},
|
||||
getFolderByUid: jest.fn(),
|
||||
setFolderTitle: jest.fn(),
|
||||
saveFolder: jest.fn(),
|
||||
deleteFolder: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = shallow(<FolderSettingsPage {...props} />);
|
||||
const instance = wrapper.instance() as FolderSettingsPage;
|
||||
|
||||
return {
|
||||
wrapper,
|
||||
instance,
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should enable save button', () => {
|
||||
const { wrapper } = setup({
|
||||
folder: {
|
||||
id: 1,
|
||||
uid: '1234',
|
||||
title: 'loading',
|
||||
canSave: true,
|
||||
hasChanged: true,
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
103
public/app/features/folders/FolderSettingsPage.tsx
Normal file
103
public/app/features/folders/FolderSettingsPage.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { NavModel, StoreState, FolderState } from 'app/types';
|
||||
import { getFolderByUid, setFolderTitle, saveFolder, deleteFolder } from './state/actions';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
folderUid: string;
|
||||
folder: FolderState;
|
||||
getFolderByUid: typeof getFolderByUid;
|
||||
setFolderTitle: typeof setFolderTitle;
|
||||
saveFolder: typeof saveFolder;
|
||||
deleteFolder: typeof deleteFolder;
|
||||
}
|
||||
|
||||
export class FolderSettingsPage extends PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
this.props.getFolderByUid(this.props.folderUid);
|
||||
}
|
||||
|
||||
onTitleChange = evt => {
|
||||
this.props.setFolderTitle(evt.target.value);
|
||||
};
|
||||
|
||||
onSave = async evt => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
await this.props.saveFolder(this.props.folder);
|
||||
};
|
||||
|
||||
onDelete = evt => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: `Do you want to delete this folder and all its dashboards?`,
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
this.props.deleteFolder(this.props.folder.uid);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navModel, folder } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
<div className="page-container page-body">
|
||||
<h2 className="page-sub-heading">Folder Settings</h2>
|
||||
|
||||
<div className="section gf-form-group">
|
||||
<form name="folderSettingsForm" onSubmit={this.onSave}>
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label width-7">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input width-30"
|
||||
value={folder.title}
|
||||
onChange={this.onTitleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
<button type="submit" className="btn btn-success" disabled={!folder.canSave || !folder.hasChanged}>
|
||||
<i className="fa fa-save" /> Save
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={this.onDelete} disabled={!folder.canSave}>
|
||||
<i className="fa fa-trash" /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => {
|
||||
const uid = state.location.routeParams.uid;
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, `folder-settings-${uid}`),
|
||||
folderUid: uid,
|
||||
folder: state.folder,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getFolderByUid,
|
||||
saveFolder,
|
||||
setFolderTitle,
|
||||
deleteFolder,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(FolderSettingsPage));
|
||||
@@ -0,0 +1,131 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should enable save button 1`] = `
|
||||
<div>
|
||||
<PageHeader
|
||||
model={Object {}}
|
||||
/>
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<h2
|
||||
className="page-sub-heading"
|
||||
>
|
||||
Folder Settings
|
||||
</h2>
|
||||
<div
|
||||
className="section gf-form-group"
|
||||
>
|
||||
<form
|
||||
name="folderSettingsForm"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-7"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
className="gf-form-input width-30"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="loading"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-button-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
disabled={false}
|
||||
type="submit"
|
||||
>
|
||||
<i
|
||||
className="fa fa-save"
|
||||
/>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-trash"
|
||||
/>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div>
|
||||
<PageHeader
|
||||
model={Object {}}
|
||||
/>
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<h2
|
||||
className="page-sub-heading"
|
||||
>
|
||||
Folder Settings
|
||||
</h2>
|
||||
<div
|
||||
className="section gf-form-group"
|
||||
>
|
||||
<form
|
||||
name="folderSettingsForm"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-7"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
className="gf-form-input width-30"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="loading"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-button-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
disabled={true}
|
||||
type="submit"
|
||||
>
|
||||
<i
|
||||
className="fa fa-save"
|
||||
/>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-trash"
|
||||
/>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
67
public/app/features/folders/state/actions.ts
Normal file
67
public/app/features/folders/state/actions.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { StoreState } from 'app/types';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { FolderDTO, FolderState } from 'app/types';
|
||||
import { updateNavIndex, updateLocation } from 'app/core/actions';
|
||||
import { buildNavModel } from './navModel';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadFolder = 'LOAD_FOLDER',
|
||||
SetFolderTitle = 'SET_FOLDER_TITLE',
|
||||
SaveFolder = 'SAVE_FOLDER',
|
||||
}
|
||||
|
||||
export interface LoadFolderAction {
|
||||
type: ActionTypes.LoadFolder;
|
||||
payload: FolderDTO;
|
||||
}
|
||||
|
||||
export interface SetFolderTitleAction {
|
||||
type: ActionTypes.SetFolderTitle;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export const loadFolder = (folder: FolderDTO): LoadFolderAction => ({
|
||||
type: ActionTypes.LoadFolder,
|
||||
payload: folder,
|
||||
});
|
||||
|
||||
export const setFolderTitle = (newTitle: string): SetFolderTitleAction => ({
|
||||
type: ActionTypes.SetFolderTitle,
|
||||
payload: newTitle,
|
||||
});
|
||||
|
||||
export type Action = LoadFolderAction | SetFolderTitleAction;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
|
||||
|
||||
|
||||
export function getFolderByUid(uid: string): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const folder = await getBackendSrv().getFolderByUid(uid);
|
||||
dispatch(loadFolder(folder));
|
||||
dispatch(updateNavIndex(buildNavModel(folder)));
|
||||
};
|
||||
}
|
||||
|
||||
export function saveFolder(folder: FolderState): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const res = await getBackendSrv().put(`/api/folders/${folder.uid}`, {
|
||||
title: folder.title,
|
||||
version: folder.version,
|
||||
});
|
||||
|
||||
// this should be redux action at some point
|
||||
appEvents.emit('alert-success', ['Folder saved']);
|
||||
|
||||
dispatch(updateLocation({ path: `${res.url}/settings` }));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteFolder(uid: string): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
await getBackendSrv().deleteFolder(uid, true);
|
||||
dispatch(updateLocation({ path: `dashboards` }));
|
||||
};
|
||||
}
|
||||
35
public/app/features/folders/state/navModel.ts
Normal file
35
public/app/features/folders/state/navModel.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { FolderDTO, NavModelItem } from 'app/types';
|
||||
|
||||
export function buildNavModel(folder: FolderDTO): NavModelItem {
|
||||
return {
|
||||
icon: 'fa fa-folder-open',
|
||||
id: 'manage-folder',
|
||||
subTitle: 'Manage folder dashboards & permissions',
|
||||
url: '',
|
||||
text: folder.title,
|
||||
breadcrumbs: [{ title: 'Dashboards', url: 'dashboards' }],
|
||||
children: [
|
||||
{
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-th-large',
|
||||
id: `folder-dashboards-${folder.uid}`,
|
||||
text: 'Dashboards',
|
||||
url: folder.url,
|
||||
},
|
||||
{
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-lock',
|
||||
id: `folder-permissions-${folder.uid}`,
|
||||
text: 'Permissions',
|
||||
url: `${folder.url}/permissions`,
|
||||
},
|
||||
{
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-cog',
|
||||
id: `folder-settings-${folder.uid}`,
|
||||
text: 'Settings',
|
||||
url: `${folder.url}/settings`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
33
public/app/features/folders/state/reducers.ts
Normal file
33
public/app/features/folders/state/reducers.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { FolderState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
|
||||
export const inititalState: FolderState = {
|
||||
id: 0,
|
||||
uid: 'loading',
|
||||
title: 'loading',
|
||||
url: '',
|
||||
canSave: false,
|
||||
hasChanged: false,
|
||||
version: 0,
|
||||
};
|
||||
|
||||
export const folderReducer = (state = inititalState, action: Action): FolderState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadFolder:
|
||||
return {
|
||||
...action.payload,
|
||||
hasChanged: false,
|
||||
};
|
||||
case ActionTypes.SetFolderTitle:
|
||||
return {
|
||||
...state,
|
||||
title: action.payload,
|
||||
hasChanged: action.payload.trim().length > 0,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default {
|
||||
folder: folderReducer,
|
||||
};
|
||||
Reference in New Issue
Block a user