fix HOC to recive objects

This commit is contained in:
zmagod 2018-01-08 16:28:20 +01:00
parent c07f54a0c7
commit c5379896c0
5 changed files with 124 additions and 41 deletions

View file

@ -15,28 +15,34 @@ module ClientApi
def generate_permissions_object
sanitize_permissions!
@permissions = {}
if @resource
@required_permissions.collect do |permission|
@permissions.merge!("#{permission}?" => @holder.eval(permission,
current_user,
@resource))
end
else
@required_permissions.collect do |permission|
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
# return false if object does not exist
result = obj ? @holder.eval('read_team', current_user, obj) : false
@permissions.merge!(permission => result)
else
@permissions.merge!(
"#{permission}?" => @holder.eval_generic(permission, current_user)
permission => @holder.eval_generic(
parsed_permision, current_user
)
)
end
end
end
def sanitize_permissions!
@required_permissions = params.fetch(:parsePermission) do
@required_permissions = params.fetch(:requiredPermissions) do
:permissions_array_missing
end
@holder = Canaid::PermissionsHolder.instance
@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
raise ArgumentError, "Method #{permission} has no related " \
"permission registered."
@ -49,7 +55,7 @@ module ClientApi
def resource_valid?
@resource = params[: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
return false
end

View file

@ -5,10 +5,18 @@ import styled from "styled-components";
import { FormattedMessage, FormattedPlural } from "react-intl";
import { Button, Glyphicon } from "react-bootstrap";
import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes";
import * as Permissions from "../../../../../services/permissions"
const Wrapper = styled.div`margin: 15px 0;`;
const TeamsPageDetails = ({ teams }) => {
const TeamsPageDetails = ({ teams, permissions }) => {
const teamsNumber = teams.length;
const newTeamButton = (
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
<Button>
<Glyphicon glyph="plus" />&nbsp;<FormattedMessage id="global_team_switch.new_team" />
</Button>
</LinkContainer>
)
return (
<Wrapper>
<FormattedPlural
@ -30,11 +38,7 @@ const TeamsPageDetails = ({ teams }) => {
/>
}
/>&nbsp;
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
<Button>
<Glyphicon glyph="plus" />&nbsp;<FormattedMessage id="global_team_switch.new_team" />
</Button>
</LinkContainer>
{permissions.can_create_teams ? newTeamButton : ''}
</Wrapper>
);
};
@ -53,7 +57,8 @@ TeamsPageDetails.propTypes = {
};
TeamsPageDetails.defaultProps = {
teams: []
teams: [],
permissions: {}
};
export default TeamsPageDetails;
export default Permissions.connect(TeamsPageDetails, ["can_create_teams"]);

View file

@ -7,10 +7,9 @@ export const getPermissionStatus = (
requiredPermissions: Array<string>,
resource: string
): Promise<*> => {
const parsePermission = requiredPermissions.map(el => el.replace("?", ""));
return axiosInstance
.post(PERMISSIONS_PATH, {
parsePermission,
requiredPermissions,
resource
})
.then(({ data }) => data);

View file

@ -7,14 +7,48 @@
Than you use the Permissions.connect method to wrap your component and
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
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 { getPermissionStatus } from "../api/permissions_api";
@ -22,15 +56,20 @@ type State = {
permissions: any
};
type ResourceObject = {
type: string,
id: number
};
type PermissionsObject = {
[string]: boolean | null
}
};
/*
This function accepts 3 arguments which are REQUIRED
1.) WrappedComponent: Component that you want to have permissions
2.) requiredPermissions: an array of strings with permissions methods that
1.) @WrappedComponent: Component that you want to have permissions
2.) @requiredPermissions: an array of strings with permissions methods that
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: {}>(
WrappedComponent: React.ComponentType<Props>,
@ -41,31 +80,55 @@ export function connect<Props: {}>(
requiredPermissions.forEach(el => {
parsedPermissions[el] = null;
});
return class extends React.Component<*, State> {
constructor(props: any) {
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 {
getPermissionStatus(requiredPermissions, resource)
.then(data => {
this.setState({ permissions: data });
})
.catch(() => {
const permissions: PermissionsObject = {};
requiredPermissions.forEach(el => {
permissions[el] = false;
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 => {
this.setState({ permissions: data, permissionsRequestDone: true });
})
.catch(() => {
const permissions: PermissionsObject = {};
permissionsArray.forEach(el => {
permissions[el] = false;
});
this.setState({ permissions });
});
this.setState({ permissions });
});
}
}
render(): any {
return (
<WrappedComponent
permissions={this.state.permissions}
setPermissionResourceId={
resource ? this.setPermissionResourceId : undefined
}
{...this.props}
/>
);

View file

@ -4,10 +4,20 @@ describe ClientApi::PermissionsController, type: :controller do
login_user
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
{ parsePermission: ['can_view_team'], resource: 'UserTeam' }
{ requiredPermissions: ['can_read_team'],
resource: { type: 'Team', id: team.id } }
end
let(:subject) { post :status, format: :json, params: params }
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