mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-07 20:40:26 +08:00
fix HOC to recive objects
This commit is contained in:
parent
c07f54a0c7
commit
c5379896c0
5 changed files with 124 additions and 41 deletions
|
@ -15,28 +15,34 @@ module ClientApi
|
||||||
def generate_permissions_object
|
def generate_permissions_object
|
||||||
sanitize_permissions!
|
sanitize_permissions!
|
||||||
@permissions = {}
|
@permissions = {}
|
||||||
|
obj = @resource.fetch(:type)
|
||||||
|
.constantize
|
||||||
|
.public_send(:find_by_id, @resource.fetch(:id) {
|
||||||
|
raise ArgumentError, 'ID must be present'
|
||||||
|
}) if @resource
|
||||||
|
@required_permissions.collect do |permission|
|
||||||
|
parsed_permision = permission.gsub('can_', '')
|
||||||
if @resource
|
if @resource
|
||||||
@required_permissions.collect do |permission|
|
# return false if object does not exist
|
||||||
@permissions.merge!("#{permission}?" => @holder.eval(permission,
|
result = obj ? @holder.eval('read_team', current_user, obj) : false
|
||||||
current_user,
|
@permissions.merge!(permission => result)
|
||||||
@resource))
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
@required_permissions.collect do |permission|
|
|
||||||
@permissions.merge!(
|
@permissions.merge!(
|
||||||
"#{permission}?" => @holder.eval_generic(permission, current_user)
|
permission => @holder.eval_generic(
|
||||||
|
parsed_permision, current_user
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize_permissions!
|
def sanitize_permissions!
|
||||||
@required_permissions = params.fetch(:parsePermission) do
|
@required_permissions = params.fetch(:requiredPermissions) do
|
||||||
:permissions_array_missing
|
:permissions_array_missing
|
||||||
end
|
end
|
||||||
@holder = Canaid::PermissionsHolder.instance
|
@holder = Canaid::PermissionsHolder.instance
|
||||||
@required_permissions.each do |permission|
|
@required_permissions.each do |permission|
|
||||||
next if @holder.has_permission?(permission)
|
next if @holder.has_permission?(permission.gsub('can_', ''))
|
||||||
# this error should happen only in development
|
# this error should happen only in development
|
||||||
raise ArgumentError, "Method #{permission} has no related " \
|
raise ArgumentError, "Method #{permission} has no related " \
|
||||||
"permission registered."
|
"permission registered."
|
||||||
|
@ -49,7 +55,7 @@ module ClientApi
|
||||||
def resource_valid?
|
def resource_valid?
|
||||||
@resource = params[:resource]
|
@resource = params[:resource]
|
||||||
return true unless @resource
|
return true unless @resource
|
||||||
return true if Object.const_get(@resource.classify)
|
return true if Object.const_get(@resource.fetch(:type).classify)
|
||||||
rescue NameError
|
rescue NameError
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,18 @@ import styled from "styled-components";
|
||||||
import { FormattedMessage, FormattedPlural } from "react-intl";
|
import { FormattedMessage, FormattedPlural } from "react-intl";
|
||||||
import { Button, Glyphicon } from "react-bootstrap";
|
import { Button, Glyphicon } from "react-bootstrap";
|
||||||
import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes";
|
import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes";
|
||||||
|
import * as Permissions from "../../../../../services/permissions"
|
||||||
|
|
||||||
const Wrapper = styled.div`margin: 15px 0;`;
|
const Wrapper = styled.div`margin: 15px 0;`;
|
||||||
const TeamsPageDetails = ({ teams }) => {
|
const TeamsPageDetails = ({ teams, permissions }) => {
|
||||||
const teamsNumber = teams.length;
|
const teamsNumber = teams.length;
|
||||||
|
const newTeamButton = (
|
||||||
|
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
|
||||||
|
<Button>
|
||||||
|
<Glyphicon glyph="plus" /> <FormattedMessage id="global_team_switch.new_team" />
|
||||||
|
</Button>
|
||||||
|
</LinkContainer>
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<FormattedPlural
|
<FormattedPlural
|
||||||
|
@ -30,11 +38,7 @@ const TeamsPageDetails = ({ teams }) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
|
{permissions.can_create_teams ? newTeamButton : ''}
|
||||||
<Button>
|
|
||||||
<Glyphicon glyph="plus" /> <FormattedMessage id="global_team_switch.new_team" />
|
|
||||||
</Button>
|
|
||||||
</LinkContainer>
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -53,7 +57,8 @@ TeamsPageDetails.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
TeamsPageDetails.defaultProps = {
|
TeamsPageDetails.defaultProps = {
|
||||||
teams: []
|
teams: [],
|
||||||
|
permissions: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TeamsPageDetails;
|
export default Permissions.connect(TeamsPageDetails, ["can_create_teams"]);
|
||||||
|
|
|
@ -7,10 +7,9 @@ export const getPermissionStatus = (
|
||||||
requiredPermissions: Array<string>,
|
requiredPermissions: Array<string>,
|
||||||
resource: string
|
resource: string
|
||||||
): Promise<*> => {
|
): Promise<*> => {
|
||||||
const parsePermission = requiredPermissions.map(el => el.replace("?", ""));
|
|
||||||
return axiosInstance
|
return axiosInstance
|
||||||
.post(PERMISSIONS_PATH, {
|
.post(PERMISSIONS_PATH, {
|
||||||
parsePermission,
|
requiredPermissions,
|
||||||
resource
|
resource
|
||||||
})
|
})
|
||||||
.then(({ data }) => data);
|
.then(({ data }) => data);
|
||||||
|
|
|
@ -7,14 +7,48 @@
|
||||||
Than you use the Permissions.connect method to wrap your component and
|
Than you use the Permissions.connect method to wrap your component and
|
||||||
pass this "permissions helper methods" in your component params:
|
pass this "permissions helper methods" in your component params:
|
||||||
|
|
||||||
|
If you need to specific model you have to specify it in the connect method
|
||||||
|
like the example below
|
||||||
>
|
>
|
||||||
> Permissions.connect(MyComponent, ["can_update_team?", "can_read_team?"], "Team");
|
> Permissions.connect(MyComponent, ["can_update_team", "can_read_team"], "Team");
|
||||||
>
|
>
|
||||||
|
|
||||||
|
In case your component is connected to Redux or some other HOC you can simply
|
||||||
|
chain the HOC's
|
||||||
|
>
|
||||||
|
> const mapStateToProps = ({ current_team }) => ({ current_team });
|
||||||
|
> const MyComponentWithPermissions = Permissions.connect(MyComponent, ["can_read_team"], "Team");
|
||||||
|
> export default connect(mapStateToProps)(MyComponentWithPermissions);
|
||||||
|
>
|
||||||
|
beside the permissions object you get the setPermissionResourceId/1 function
|
||||||
|
in your component props. Than you can pass the id of the resource when you have it.
|
||||||
|
|
||||||
|
Example: you need the current team for whatever reason you can call it in the
|
||||||
|
shouldComponentUpdate function...
|
||||||
|
|
||||||
|
> shouldComponentUpdate(nextProps: any, nextState: any) {
|
||||||
|
> this.props.setPermissionResourceId(this.props.current_team.id);
|
||||||
|
> return true; // remember to return true!
|
||||||
|
> }
|
||||||
|
|
||||||
|
JUST REMEMBER THAT YOU HAVE PASS A VALID ID and you have to check if is present
|
||||||
|
at the moment of execution. THE REQUEST WILL BE TRIGGERED WHEN YOU INVOKE
|
||||||
|
setPermissionResourceId/1 FUNCTION!!!!
|
||||||
|
|
||||||
|
else you can pass just the perrmissions on the user
|
||||||
|
>
|
||||||
|
> Permissions.connect(MyComponent, ["can_update"])
|
||||||
|
>
|
||||||
|
THE REQUEST WILL BE TRIGGERED WHEN THE COMPONENT MOUNTS!!!
|
||||||
|
|
||||||
Now you can access your permissions through component params. The permissions
|
Now you can access your permissions through component params. The permissions
|
||||||
you required have 3 states [true, false, null]. Null is when you are waiting for server response.
|
you required have 3 states [true, false, null]. Null is when you are waiting for server response.
|
||||||
You can use methods params.can_update_team? or whatever permissions you declare
|
|
||||||
|
Finally you can access to your permissions in the component using
|
||||||
|
props.permissions.can_.... whatever you specify. The props.permissions is
|
||||||
|
basically an object that holds all required permissions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { getPermissionStatus } from "../api/permissions_api";
|
import { getPermissionStatus } from "../api/permissions_api";
|
||||||
|
|
||||||
|
@ -22,15 +56,20 @@ type State = {
|
||||||
permissions: any
|
permissions: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResourceObject = {
|
||||||
|
type: string,
|
||||||
|
id: number
|
||||||
|
};
|
||||||
|
|
||||||
type PermissionsObject = {
|
type PermissionsObject = {
|
||||||
[string]: boolean | null
|
[string]: boolean | null
|
||||||
}
|
};
|
||||||
/*
|
/*
|
||||||
This function accepts 3 arguments which are REQUIRED
|
This function accepts 3 arguments which are REQUIRED
|
||||||
1.) WrappedComponent: Component that you want to have permissions
|
1.) @WrappedComponent: Component that you want to have permissions
|
||||||
2.) requiredPermissions: an array of strings with permissions methods that
|
2.) @requiredPermissions: an array of strings with permissions methods that
|
||||||
will be available in your component
|
will be available in your component
|
||||||
3.) resource: a string of reference/model name
|
3.) @resource: a string of reference/model name
|
||||||
*/
|
*/
|
||||||
export function connect<Props: {}>(
|
export function connect<Props: {}>(
|
||||||
WrappedComponent: React.ComponentType<Props>,
|
WrappedComponent: React.ComponentType<Props>,
|
||||||
|
@ -41,31 +80,55 @@ export function connect<Props: {}>(
|
||||||
requiredPermissions.forEach(el => {
|
requiredPermissions.forEach(el => {
|
||||||
parsedPermissions[el] = null;
|
parsedPermissions[el] = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return class extends React.Component<*, State> {
|
return class extends React.Component<*, State> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { permissions: parsedPermissions };
|
this.state = {
|
||||||
|
permissions: parsedPermissions,
|
||||||
|
permissionsRequestDone: false
|
||||||
|
};
|
||||||
|
(this: any).getPermissions = this.getPermissions.bind(this);
|
||||||
|
(this: any).setPermissionResourceId = this.setPermissionResourceId.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
getPermissionStatus(requiredPermissions, resource)
|
if (!resource) {
|
||||||
|
this.getPermissions(requiredPermissions, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPermissionResourceId(id: number): void {
|
||||||
|
this.getPermissions(requiredPermissions, { type: resource, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
getPermissions(
|
||||||
|
permissionsArray: Array<string>,
|
||||||
|
resourceObject: ResourceObject | undefined
|
||||||
|
): void {
|
||||||
|
if (!this.state.permissionsRequestDone) {
|
||||||
|
getPermissionStatus(permissionsArray, resourceObject)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.setState({ permissions: data });
|
this.setState({ permissions: data, permissionsRequestDone: true });
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
const permissions: PermissionsObject = {};
|
const permissions: PermissionsObject = {};
|
||||||
requiredPermissions.forEach(el => {
|
permissionsArray.forEach(el => {
|
||||||
permissions[el] = false;
|
permissions[el] = false;
|
||||||
});
|
});
|
||||||
this.setState({ permissions });
|
this.setState({ permissions });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render(): any {
|
render(): any {
|
||||||
return (
|
return (
|
||||||
<WrappedComponent
|
<WrappedComponent
|
||||||
permissions={this.state.permissions}
|
permissions={this.state.permissions}
|
||||||
|
setPermissionResourceId={
|
||||||
|
resource ? this.setPermissionResourceId : undefined
|
||||||
|
}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,10 +4,20 @@ describe ClientApi::PermissionsController, type: :controller do
|
||||||
login_user
|
login_user
|
||||||
|
|
||||||
describe '#status' do
|
describe '#status' do
|
||||||
|
let!(:user) { User.first || create(:user) }
|
||||||
|
let!(:team) { create :team, created_by: user }
|
||||||
|
let!(:user_team) { create :user_team, user: user, team: team, role: 2 }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ parsePermission: ['can_view_team'], resource: 'UserTeam' }
|
{ requiredPermissions: ['can_read_team'],
|
||||||
|
resource: { type: 'Team', id: team.id } }
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:subject) { post :status, format: :json, params: params }
|
let(:subject) { post :status, format: :json, params: params }
|
||||||
it { is_expected.to be_success }
|
it { is_expected.to be_success }
|
||||||
|
|
||||||
|
it 'returns an object with the permission' do
|
||||||
|
body = JSON.parse(subject.body)
|
||||||
|
expect(body).to eq('can_read_team' => true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue