mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +00:00 
			
		
		
		
	✨ Feat : Add New roles and policy UI - Part 1 (#6651)
* ✨ Feat : Add New roles and policy UI
* Add rules column
* Remove rules column
* Add roles detail page
* Add policy details page
* Fix table columns width
* Add roles column to policies table
* Minor change
* Addressing review comments
* Add support for add role
			
			
This commit is contained in:
		
							parent
							
								
									455c2d039e
								
							
						
					
					
						commit
						18d204d883
					
				@ -0,0 +1,8 @@
 | 
			
		||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
<path d="M12 16.0011C11.9333 16.0011 11.8667 15.9878 11.804 15.9611C11.6493 15.8951 8 14.3058 8 11.0371V8.35847C8 8.14447 8.136 7.95447 8.33933 7.88514L11.8393 6.69447C11.9433 6.65914 12.0567 6.65914 12.1607 6.69447L15.6607 7.88514C15.864 7.95447 16 8.14447 16 8.35847V11.0371C16 14.3058 12.3507 15.8951 12.196 15.9618C12.1333 15.9878 12.0667 16.0011 12 16.0011V16.0011ZM9 8.71647V11.0365C9 13.2585 11.304 14.5945 12 14.9465C12.696 14.5945 15 13.2585 15 11.0365V8.71647L12 7.6958L9 8.71647Z" fill="#37352F"/>
 | 
			
		||||
<path d="M11.8332 12.9987C11.7012 12.9987 11.5732 12.9467 11.4798 12.852L10.1465 11.5187C9.95117 11.3233 9.95117 11.0067 10.1465 10.8113C10.3418 10.616 10.6585 10.616 10.8538 10.8113L11.7798 11.7373L13.4332 9.53198C13.5972 9.31132 13.9118 9.26598 14.1332 9.43265C14.3538 9.59798 14.3985 9.91132 14.2325 10.1327L12.2325 12.7993C12.1458 12.9153 12.0132 12.9873 11.8678 12.998C11.8565 12.998 11.8452 12.9987 11.8332 12.9987Z" fill="#37352F"/>
 | 
			
		||||
<path d="M7.06 14H1.83333C0.822 14 0 13.178 0 12.1667V1.83333C0 0.822 0.822 0 1.83333 0H9.5C10.5113 0 11.3333 0.822 11.3333 1.83333V5.45333C11.3333 5.72933 11.1093 5.95333 10.8333 5.95333C10.5573 5.95333 10.3333 5.72933 10.3333 5.45333V1.83333C10.3333 1.374 9.95933 1 9.5 1H1.83333C1.374 1 1 1.374 1 1.83333V12.1667C1 12.626 1.374 13 1.83333 13H7.06C7.336 13 7.56 13.224 7.56 13.5C7.56 13.776 7.336 14 7.06 14Z" fill="#37352F"/>
 | 
			
		||||
<path d="M8.83333 6.33203H2.5C2.224 6.33203 2 6.10803 2 5.83203C2 5.55603 2.224 5.33203 2.5 5.33203H8.83333C9.10933 5.33203 9.33333 5.55603 9.33333 5.83203C9.33333 6.10803 9.10933 6.33203 8.83333 6.33203Z" fill="#37352F"/>
 | 
			
		||||
<path d="M6.16667 9H2.5C2.224 9 2 8.776 2 8.5C2 8.224 2.224 8 2.5 8H6.16667C6.44267 8 6.66667 8.224 6.66667 8.5C6.66667 8.776 6.44267 9 6.16667 9Z" fill="#37352F"/>
 | 
			
		||||
<path d="M5.5 3.66797H2.5C2.224 3.66797 2 3.44397 2 3.16797C2 2.89197 2.224 2.66797 2.5 2.66797H5.5C5.776 2.66797 6 2.89197 6 3.16797C6 3.44397 5.776 3.66797 5.5 3.66797Z" fill="#37352F"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.0 KiB  | 
@ -0,0 +1,88 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { AxiosResponse } from 'axios';
 | 
			
		||||
import { CreateRole } from '../generated/api/teams/createRole';
 | 
			
		||||
import { Policy } from '../generated/entity/policies/policy';
 | 
			
		||||
import { Role } from '../generated/entity/teams/role';
 | 
			
		||||
import { Paging } from '../generated/type/paging';
 | 
			
		||||
import APIClient from './index';
 | 
			
		||||
 | 
			
		||||
export const getRoles = async (
 | 
			
		||||
  fields: string,
 | 
			
		||||
  after?: string,
 | 
			
		||||
  before?: string,
 | 
			
		||||
  defaultRoles = false,
 | 
			
		||||
  limit = 10
 | 
			
		||||
) => {
 | 
			
		||||
  const params = {
 | 
			
		||||
    default: defaultRoles,
 | 
			
		||||
    limit,
 | 
			
		||||
    fields,
 | 
			
		||||
    after,
 | 
			
		||||
    before,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.get<{ data: Role[]; paging: Paging }>(
 | 
			
		||||
    '/roles',
 | 
			
		||||
    { params }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getPolicies = async (
 | 
			
		||||
  fields: string,
 | 
			
		||||
  after?: string,
 | 
			
		||||
  before?: string,
 | 
			
		||||
  limit = 10
 | 
			
		||||
) => {
 | 
			
		||||
  const params = {
 | 
			
		||||
    limit,
 | 
			
		||||
    fields,
 | 
			
		||||
    after,
 | 
			
		||||
    before,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.get<{ data: Policy[]; paging: Paging }>(
 | 
			
		||||
    '/policies',
 | 
			
		||||
    { params }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getRoleByName = async (name: string, fields: string) => {
 | 
			
		||||
  const response = await APIClient.get<Role>(`/roles/name/${name}`, {
 | 
			
		||||
    params: { fields },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getPolicyByName = async (name: string, fields: string) => {
 | 
			
		||||
  const response = await APIClient.get<Policy>(`/policies/name/${name}`, {
 | 
			
		||||
    params: { fields },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addRole = async (data: CreateRole) => {
 | 
			
		||||
  const dataResponse = await APIClient.post<CreateRole, AxiosResponse<Role>>(
 | 
			
		||||
    '/roles',
 | 
			
		||||
    data
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return dataResponse.data;
 | 
			
		||||
};
 | 
			
		||||
@ -226,6 +226,7 @@ export const ROUTES = {
 | 
			
		||||
  TASK_DETAIL: `/tasks/${PLACEHOLDER_TASK_ID}`,
 | 
			
		||||
 | 
			
		||||
  ACTIVITY_PUSH_FEED: '/api/v1/push/feed',
 | 
			
		||||
  ADD_ROLE: '/settings/access/roles/add-role',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const SOCKET_EVENTS = {
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ export const GLOBAL_SETTINGS_MENU = [
 | 
			
		||||
      { label: 'Users', isProtected: true, icon: Icons.USERS },
 | 
			
		||||
      { label: 'Admins', isProtected: true, icon: Icons.USERS },
 | 
			
		||||
      { label: 'Roles', isProtected: true, icon: Icons.ROLE_GREY },
 | 
			
		||||
      { label: 'Policies', isProtected: true, icon: Icons.POLICIES },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2022 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.policies-detail {
 | 
			
		||||
  .roles-list-table {
 | 
			
		||||
    .ant-table-thead > tr > th {
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .ant-table-row .ant-table-cell:first-child,
 | 
			
		||||
    .ant-table-thead .ant-table-cell:first-child {
 | 
			
		||||
      padding-left: 16px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table {
 | 
			
		||||
      border: 1px solid #dde3ea;
 | 
			
		||||
      box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.12);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,174 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Card, Col, Empty, Row, Table, Tabs } from 'antd';
 | 
			
		||||
import { ColumnsType } from 'antd/lib/table';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import { isEmpty, uniqueId } from 'lodash';
 | 
			
		||||
import React, { useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { Link, useParams } from 'react-router-dom';
 | 
			
		||||
import { getPolicyByName } from '../../../axiosAPIs/rolesAPIV1';
 | 
			
		||||
import Description from '../../../components/common/description/Description';
 | 
			
		||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
 | 
			
		||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
 | 
			
		||||
import Loader from '../../../components/Loader/Loader';
 | 
			
		||||
import {
 | 
			
		||||
  GlobalSettingOptions,
 | 
			
		||||
  GlobalSettingsMenuCategory,
 | 
			
		||||
} from '../../../constants/globalSettings.constants';
 | 
			
		||||
import { Policy } from '../../../generated/entity/policies/policy';
 | 
			
		||||
import { EntityReference } from '../../../generated/type/entityReference';
 | 
			
		||||
import { getEntityName } from '../../../utils/CommonUtils';
 | 
			
		||||
import { getRoleWithFqnPath, getSettingPath } from '../../../utils/RouterUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import './PoliciesDetail.less';
 | 
			
		||||
 | 
			
		||||
const { TabPane } = Tabs;
 | 
			
		||||
 | 
			
		||||
const RolesList = ({ roles }: { roles: EntityReference[] }) => {
 | 
			
		||||
  const columns: ColumnsType<EntityReference> = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Name',
 | 
			
		||||
        dataIndex: 'name',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'name',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <Link
 | 
			
		||||
            className="hover:tw-underline tw-cursor-pointer"
 | 
			
		||||
            to={getRoleWithFqnPath(record.fullyQualifiedName || '')}>
 | 
			
		||||
            {getEntityName(record)}
 | 
			
		||||
          </Link>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Description',
 | 
			
		||||
        dataIndex: 'description',
 | 
			
		||||
        key: 'description',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <RichTextEditorPreviewer markdown={record?.description || ''} />
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      className="roles-list-table"
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      dataSource={roles}
 | 
			
		||||
      pagination={false}
 | 
			
		||||
      size="middle"
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const PoliciesDetailPage = () => {
 | 
			
		||||
  const { fqn } = useParams<{ fqn: string }>();
 | 
			
		||||
 | 
			
		||||
  const [policy, setPolicy] = useState<Policy>({} as Policy);
 | 
			
		||||
  const [isLoading, setLoading] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
  const breadcrumb = useMemo(
 | 
			
		||||
    () => [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Policies',
 | 
			
		||||
        url: getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.POLICIES
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: fqn,
 | 
			
		||||
        url: '',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [fqn]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const fetchPolicy = async () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await getPolicyByName(fqn, 'owner,location,teams,roles');
 | 
			
		||||
      setPolicy(data ?? ({} as Policy));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchPolicy();
 | 
			
		||||
  }, [fqn]);
 | 
			
		||||
 | 
			
		||||
  if (isLoading) {
 | 
			
		||||
    return <Loader />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div data-testid="policy-details-container">
 | 
			
		||||
      <TitleBreadcrumb titleLinks={breadcrumb} />
 | 
			
		||||
      {isEmpty(policy) ? (
 | 
			
		||||
        <Empty description={`No policy found for ${fqn}`} />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div className="policies-detail" data-testid="policy-details">
 | 
			
		||||
          <div className="tw--ml-5">
 | 
			
		||||
            <Description description={policy.description || ''} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <Tabs defaultActiveKey="roles">
 | 
			
		||||
            <TabPane key="roles" tab="Roles">
 | 
			
		||||
              <RolesList roles={policy.roles ?? []} />
 | 
			
		||||
            </TabPane>
 | 
			
		||||
            <TabPane key="teams" tab="Teams">
 | 
			
		||||
              {isEmpty(policy.teams) ? (
 | 
			
		||||
                <Empty description="No teams found" />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Row gutter={[16, 16]}>
 | 
			
		||||
                  {policy.teams?.map((team) => (
 | 
			
		||||
                    <Col key={uniqueId()} span={6}>
 | 
			
		||||
                      <Card title={getEntityName(team)}>
 | 
			
		||||
                        <RichTextEditorPreviewer
 | 
			
		||||
                          markdown={team.description || ''}
 | 
			
		||||
                        />
 | 
			
		||||
                      </Card>
 | 
			
		||||
                    </Col>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Row>
 | 
			
		||||
              )}
 | 
			
		||||
            </TabPane>
 | 
			
		||||
            <TabPane key="rules" tab="Rules">
 | 
			
		||||
              {isEmpty(policy.rules) ? (
 | 
			
		||||
                <Empty description="No rules found" />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Row gutter={[16, 16]}>
 | 
			
		||||
                  {policy.rules.map((rule) => (
 | 
			
		||||
                    <Col key={uniqueId()} span={8}>
 | 
			
		||||
                      <Card title={rule.name}>
 | 
			
		||||
                        <RichTextEditorPreviewer
 | 
			
		||||
                          markdown={rule.description || ''}
 | 
			
		||||
                        />
 | 
			
		||||
                      </Card>
 | 
			
		||||
                    </Col>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Row>
 | 
			
		||||
              )}
 | 
			
		||||
            </TabPane>
 | 
			
		||||
          </Tabs>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default PoliciesDetailPage;
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2022 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.policies-list-container {
 | 
			
		||||
  .ant-btn {
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.policies-list-table {
 | 
			
		||||
  .ant-table-thead > tr > th {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ant-table-row .ant-table-cell:first-child,
 | 
			
		||||
  .ant-table-thead .ant-table-cell:first-child {
 | 
			
		||||
    padding-left: 16px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  table {
 | 
			
		||||
    border: 1px solid #dde3ea;
 | 
			
		||||
    box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.12);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Space, Table } from 'antd';
 | 
			
		||||
import { ColumnsType } from 'antd/lib/table';
 | 
			
		||||
import { uniqueId } from 'lodash';
 | 
			
		||||
import React, { FC, useMemo } from 'react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
 | 
			
		||||
import { Policy } from '../../../generated/entity/policies/policy';
 | 
			
		||||
import { getEntityName } from '../../../utils/CommonUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getPolicyWithFqnPath,
 | 
			
		||||
  getRoleWithFqnPath,
 | 
			
		||||
} from '../../../utils/RouterUtils';
 | 
			
		||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
 | 
			
		||||
 | 
			
		||||
interface PolicyListProps {
 | 
			
		||||
  policies: Policy[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PoliciesList: FC<PolicyListProps> = ({ policies }) => {
 | 
			
		||||
  const columns: ColumnsType<Policy> = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Name',
 | 
			
		||||
        dataIndex: 'name',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'name',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <Link
 | 
			
		||||
            className="hover:tw-underline tw-cursor-pointer"
 | 
			
		||||
            to={getPolicyWithFqnPath(record.fullyQualifiedName || '')}>
 | 
			
		||||
            {getEntityName(record)}
 | 
			
		||||
          </Link>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Description',
 | 
			
		||||
        dataIndex: 'description',
 | 
			
		||||
        key: 'description',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <RichTextEditorPreviewer markdown={record?.description || ''} />
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Roles',
 | 
			
		||||
        dataIndex: 'roles',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'roles',
 | 
			
		||||
        render: (_, record) => {
 | 
			
		||||
          return record.roles?.length ? (
 | 
			
		||||
            <Space wrap size={4}>
 | 
			
		||||
              {record.roles.map((role) => (
 | 
			
		||||
                <Link
 | 
			
		||||
                  key={uniqueId()}
 | 
			
		||||
                  to={getRoleWithFqnPath(role.fullyQualifiedName || '')}>
 | 
			
		||||
                  {getEntityName(role)}
 | 
			
		||||
                </Link>
 | 
			
		||||
              ))}
 | 
			
		||||
            </Space>
 | 
			
		||||
          ) : (
 | 
			
		||||
            '-- '
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Actions',
 | 
			
		||||
        dataIndex: 'actions',
 | 
			
		||||
        width: '80px',
 | 
			
		||||
        key: 'actions',
 | 
			
		||||
        render: () => {
 | 
			
		||||
          return <SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />;
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      className="policies-list-table"
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      dataSource={policies}
 | 
			
		||||
      pagination={false}
 | 
			
		||||
      size="middle"
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default PoliciesList;
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Button, Col, Row, Space } from 'antd';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { getPolicies } from '../../../axiosAPIs/rolesAPIV1';
 | 
			
		||||
import Loader from '../../../components/Loader/Loader';
 | 
			
		||||
import { Policy } from '../../../generated/entity/policies/policy';
 | 
			
		||||
import { Paging } from '../../../generated/type/paging';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import PoliciesList from './PoliciesList';
 | 
			
		||||
import './PoliciesList.less';
 | 
			
		||||
 | 
			
		||||
const PoliciesListPage = () => {
 | 
			
		||||
  const [policies, setPolicies] = useState<Policy[]>([]);
 | 
			
		||||
  const [isLoading, setIsLoading] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
  const fetchPolicies = async (paging?: Paging) => {
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await getPolicies(
 | 
			
		||||
        'owner,location,roles,teams',
 | 
			
		||||
        paging?.after,
 | 
			
		||||
        paging?.before
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      setPolicies(data.data || []);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchPolicies();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return isLoading ? (
 | 
			
		||||
    <Loader />
 | 
			
		||||
  ) : (
 | 
			
		||||
    <Row className="policies-list-container" gutter={[16, 16]}>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <Space align="center" className="tw-w-full tw-justify-end" size={16}>
 | 
			
		||||
          <Button type="primary">Add Policy</Button>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <PoliciesList policies={policies} />
 | 
			
		||||
      </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default PoliciesListPage;
 | 
			
		||||
@ -0,0 +1,167 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Button, Card, Col, Form, Input, Row, Select, Space } from 'antd';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import { addRole, getPolicies } from '../../../axiosAPIs/rolesAPIV1';
 | 
			
		||||
import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor';
 | 
			
		||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
 | 
			
		||||
import {
 | 
			
		||||
  GlobalSettingOptions,
 | 
			
		||||
  GlobalSettingsMenuCategory,
 | 
			
		||||
} from '../../../constants/globalSettings.constants';
 | 
			
		||||
import { Policy } from '../../../generated/entity/policies/policy';
 | 
			
		||||
import { getRoleWithFqnPath, getSettingPath } from '../../../utils/RouterUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
const { Option } = Select;
 | 
			
		||||
const breadcrumb = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Roles',
 | 
			
		||||
    url: getSettingPath(
 | 
			
		||||
      GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
      GlobalSettingOptions.ROLES
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Add New Role',
 | 
			
		||||
    url: '',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const AddRolePage = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const [policies, setPolicies] = useState<Policy[]>([]);
 | 
			
		||||
  const [name, setName] = useState<string>('');
 | 
			
		||||
  const [description, setDescription] = useState<string>('');
 | 
			
		||||
  const [selectedPolicies, setSelectedPolicies] = useState<string[]>([]);
 | 
			
		||||
 | 
			
		||||
  const fetchPolicies = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await getPolicies('owner,location,roles,teams');
 | 
			
		||||
 | 
			
		||||
      setPolicies(data.data || []);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCancel = () => {
 | 
			
		||||
    history.push(
 | 
			
		||||
      getSettingPath(
 | 
			
		||||
        GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
        GlobalSettingOptions.ROLES
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSumbit = async () => {
 | 
			
		||||
    const data = {
 | 
			
		||||
      name,
 | 
			
		||||
      description,
 | 
			
		||||
      policies: selectedPolicies.map((policy) => ({
 | 
			
		||||
        id: policy,
 | 
			
		||||
        type: 'policy',
 | 
			
		||||
      })),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const dataResponse = await addRole(data);
 | 
			
		||||
      if (dataResponse) {
 | 
			
		||||
        history.push(getRoleWithFqnPath(dataResponse.fullyQualifiedName || ''));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchPolicies();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Row gutter={[16, 16]}>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <TitleBreadcrumb titleLinks={breadcrumb} />
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={18}>
 | 
			
		||||
        <Card title="Add New Role">
 | 
			
		||||
          <Form
 | 
			
		||||
            data-testid="role-form"
 | 
			
		||||
            id="role-form"
 | 
			
		||||
            layout="vertical"
 | 
			
		||||
            onFinish={handleSumbit}>
 | 
			
		||||
            <Form.Item
 | 
			
		||||
              label="Name:"
 | 
			
		||||
              name="name"
 | 
			
		||||
              rules={[
 | 
			
		||||
                {
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  max: 128,
 | 
			
		||||
                  min: 1,
 | 
			
		||||
                },
 | 
			
		||||
              ]}>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder="Role name"
 | 
			
		||||
                type="text"
 | 
			
		||||
                value={name}
 | 
			
		||||
                onChange={(e) => setName(e.target.value)}
 | 
			
		||||
              />
 | 
			
		||||
            </Form.Item>
 | 
			
		||||
            <Form.Item label="Description:" name="description">
 | 
			
		||||
              <RichTextEditor
 | 
			
		||||
                height="200px"
 | 
			
		||||
                initialValue={description}
 | 
			
		||||
                placeHolder="write your description"
 | 
			
		||||
                style={{ margin: 0 }}
 | 
			
		||||
                onTextChange={(value) => setDescription(value)}
 | 
			
		||||
              />
 | 
			
		||||
            </Form.Item>
 | 
			
		||||
            <Form.Item
 | 
			
		||||
              label="Select a policy:"
 | 
			
		||||
              name="policies"
 | 
			
		||||
              rules={[
 | 
			
		||||
                {
 | 
			
		||||
                  required: true,
 | 
			
		||||
                },
 | 
			
		||||
              ]}>
 | 
			
		||||
              <Select
 | 
			
		||||
                mode="multiple"
 | 
			
		||||
                placeholder="Select Policy"
 | 
			
		||||
                value={selectedPolicies}
 | 
			
		||||
                onChange={(values) => setSelectedPolicies(values)}>
 | 
			
		||||
                {policies.map((policy) => (
 | 
			
		||||
                  <Option key={policy.id}>
 | 
			
		||||
                    {policy.displayName || policy.name}
 | 
			
		||||
                  </Option>
 | 
			
		||||
                ))}
 | 
			
		||||
              </Select>
 | 
			
		||||
            </Form.Item>
 | 
			
		||||
 | 
			
		||||
            <Space align="center" className="tw-w-full tw-justify-end">
 | 
			
		||||
              <Button type="link" onClick={handleCancel}>
 | 
			
		||||
                Cancel
 | 
			
		||||
              </Button>
 | 
			
		||||
              <Button form="role-form" htmlType="submit" type="primary">
 | 
			
		||||
                Submit
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Space>
 | 
			
		||||
          </Form>
 | 
			
		||||
        </Card>
 | 
			
		||||
      </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AddRolePage;
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2022 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.roles-detail {
 | 
			
		||||
  .policies-list-table {
 | 
			
		||||
    .ant-table-thead > tr > th {
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .ant-table-row .ant-table-cell:first-child,
 | 
			
		||||
    .ant-table-thead .ant-table-cell:first-child {
 | 
			
		||||
      padding-left: 16px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table {
 | 
			
		||||
      border: 1px solid #dde3ea;
 | 
			
		||||
      box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.12);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,177 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Card, Col, Empty, Row, Table, Tabs } from 'antd';
 | 
			
		||||
import { ColumnsType } from 'antd/lib/table';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import { isEmpty, uniqueId } from 'lodash';
 | 
			
		||||
import { EntityReference } from 'Models';
 | 
			
		||||
import React, { useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { Link, useParams } from 'react-router-dom';
 | 
			
		||||
import { getRoleByName } from '../../../axiosAPIs/rolesAPIV1';
 | 
			
		||||
import Description from '../../../components/common/description/Description';
 | 
			
		||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
 | 
			
		||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
 | 
			
		||||
import Loader from '../../../components/Loader/Loader';
 | 
			
		||||
import {
 | 
			
		||||
  GlobalSettingOptions,
 | 
			
		||||
  GlobalSettingsMenuCategory,
 | 
			
		||||
} from '../../../constants/globalSettings.constants';
 | 
			
		||||
import { Role } from '../../../generated/entity/teams/role';
 | 
			
		||||
import { getEntityName } from '../../../utils/CommonUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getPolicyWithFqnPath,
 | 
			
		||||
  getSettingPath,
 | 
			
		||||
} from '../../../utils/RouterUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import './RolesDetail.less';
 | 
			
		||||
 | 
			
		||||
const { TabPane } = Tabs;
 | 
			
		||||
 | 
			
		||||
const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
 | 
			
		||||
  const columns: ColumnsType<EntityReference> = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Name',
 | 
			
		||||
        dataIndex: 'name',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'name',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <Link
 | 
			
		||||
            className="hover:tw-underline tw-cursor-pointer"
 | 
			
		||||
            to={getPolicyWithFqnPath(record.fullyQualifiedName || '')}>
 | 
			
		||||
            {getEntityName(record)}
 | 
			
		||||
          </Link>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Description',
 | 
			
		||||
        dataIndex: 'description',
 | 
			
		||||
        key: 'description',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <RichTextEditorPreviewer markdown={record?.description || ''} />
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      className="policies-list-table"
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      dataSource={policies}
 | 
			
		||||
      pagination={false}
 | 
			
		||||
      size="middle"
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const RolesDetailPage = () => {
 | 
			
		||||
  const { fqn } = useParams<{ fqn: string }>();
 | 
			
		||||
 | 
			
		||||
  const [role, setRole] = useState<Role>({} as Role);
 | 
			
		||||
  const [isLoading, setLoading] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
  const breadcrumb = useMemo(
 | 
			
		||||
    () => [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'Roles',
 | 
			
		||||
        url: getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.ROLES
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: fqn,
 | 
			
		||||
        url: '',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [fqn]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const fetchRole = async () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await getRoleByName(fqn, 'policies,teams,users');
 | 
			
		||||
      setRole(data ?? ({} as Role));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchRole();
 | 
			
		||||
  }, [fqn]);
 | 
			
		||||
 | 
			
		||||
  if (isLoading) {
 | 
			
		||||
    return <Loader />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div data-testid="role-details-container">
 | 
			
		||||
      <TitleBreadcrumb titleLinks={breadcrumb} />
 | 
			
		||||
      {isEmpty(role) ? (
 | 
			
		||||
        <Empty description={`No roles found for ${fqn}`} />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div className="roles-detail" data-testid="role-details">
 | 
			
		||||
          <div className="tw--ml-5">
 | 
			
		||||
            <Description description={role.description || ''} />
 | 
			
		||||
          </div>
 | 
			
		||||
          <Tabs defaultActiveKey="policies">
 | 
			
		||||
            <TabPane key="policies" tab="Policies">
 | 
			
		||||
              <PoliciesList policies={role.policies ?? []} />
 | 
			
		||||
            </TabPane>
 | 
			
		||||
            <TabPane key="teams" tab="Teams">
 | 
			
		||||
              {isEmpty(role.teams) ? (
 | 
			
		||||
                <Empty description="No teams found" />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Row gutter={[16, 16]}>
 | 
			
		||||
                  {role.teams?.map((team) => (
 | 
			
		||||
                    <Col key={uniqueId()} span={6}>
 | 
			
		||||
                      <Card title={getEntityName(team)}>
 | 
			
		||||
                        <RichTextEditorPreviewer
 | 
			
		||||
                          markdown={team.description || ''}
 | 
			
		||||
                        />
 | 
			
		||||
                      </Card>
 | 
			
		||||
                    </Col>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Row>
 | 
			
		||||
              )}
 | 
			
		||||
            </TabPane>
 | 
			
		||||
            <TabPane key="users" tab="Users">
 | 
			
		||||
              {isEmpty(role.users) ? (
 | 
			
		||||
                <Empty description="No users found" />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Row gutter={[16, 16]}>
 | 
			
		||||
                  {role.users?.map((user) => (
 | 
			
		||||
                    <Col key={uniqueId()} span={6}>
 | 
			
		||||
                      <Card title={getEntityName(user)}>
 | 
			
		||||
                        <RichTextEditorPreviewer
 | 
			
		||||
                          markdown={user.description || ''}
 | 
			
		||||
                        />
 | 
			
		||||
                      </Card>
 | 
			
		||||
                    </Col>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </Row>
 | 
			
		||||
              )}
 | 
			
		||||
            </TabPane>
 | 
			
		||||
          </Tabs>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default RolesDetailPage;
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2022 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.roles-list-container {
 | 
			
		||||
  .ant-btn {
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.roles-list-table {
 | 
			
		||||
  .ant-table-thead > tr > th {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ant-table-row .ant-table-cell:first-child,
 | 
			
		||||
  .ant-table-thead .ant-table-cell:first-child {
 | 
			
		||||
    padding-left: 16px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  table {
 | 
			
		||||
    border: 1px solid #dde3ea;
 | 
			
		||||
    box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.12);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Space, Table } from 'antd';
 | 
			
		||||
import { ColumnsType } from 'antd/lib/table';
 | 
			
		||||
import { uniqueId } from 'lodash';
 | 
			
		||||
import React, { FC, useMemo } from 'react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
 | 
			
		||||
import { Role } from '../../../generated/entity/teams/role';
 | 
			
		||||
import { getEntityName } from '../../../utils/CommonUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getPolicyWithFqnPath,
 | 
			
		||||
  getRoleWithFqnPath,
 | 
			
		||||
} from '../../../utils/RouterUtils';
 | 
			
		||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
 | 
			
		||||
 | 
			
		||||
interface RolesListProps {
 | 
			
		||||
  roles: Role[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const RolesList: FC<RolesListProps> = ({ roles }) => {
 | 
			
		||||
  const columns: ColumnsType<Role> = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Name',
 | 
			
		||||
        dataIndex: 'name',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'name',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <Link
 | 
			
		||||
            className="hover:tw-underline tw-cursor-pointer"
 | 
			
		||||
            to={getRoleWithFqnPath(record.fullyQualifiedName || '')}>
 | 
			
		||||
            {getEntityName(record)}
 | 
			
		||||
          </Link>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Description',
 | 
			
		||||
        dataIndex: 'description',
 | 
			
		||||
        key: 'description',
 | 
			
		||||
        render: (_, record) => (
 | 
			
		||||
          <RichTextEditorPreviewer markdown={record?.description || ''} />
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Policies',
 | 
			
		||||
        dataIndex: 'policies',
 | 
			
		||||
        width: '200px',
 | 
			
		||||
        key: 'policies',
 | 
			
		||||
        render: (_, record) => {
 | 
			
		||||
          return record.policies?.length ? (
 | 
			
		||||
            <Space wrap size={4}>
 | 
			
		||||
              {record.policies.map((policy) => (
 | 
			
		||||
                <Link
 | 
			
		||||
                  key={uniqueId()}
 | 
			
		||||
                  to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}>
 | 
			
		||||
                  {getEntityName(policy)}
 | 
			
		||||
                </Link>
 | 
			
		||||
              ))}
 | 
			
		||||
            </Space>
 | 
			
		||||
          ) : (
 | 
			
		||||
            '-- '
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: 'Actions',
 | 
			
		||||
        dataIndex: 'actions',
 | 
			
		||||
        width: '80px',
 | 
			
		||||
        key: 'actions',
 | 
			
		||||
        render: () => {
 | 
			
		||||
          return <SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />;
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      className="roles-list-table"
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      dataSource={roles}
 | 
			
		||||
      pagination={false}
 | 
			
		||||
      size="middle"
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default RolesList;
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2021 Collate
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Button, Col, Row, Space } from 'antd';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import { getRoles } from '../../../axiosAPIs/rolesAPIV1';
 | 
			
		||||
import Loader from '../../../components/Loader/Loader';
 | 
			
		||||
import { ROUTES } from '../../../constants/constants';
 | 
			
		||||
import { Role } from '../../../generated/entity/teams/role';
 | 
			
		||||
import { Paging } from '../../../generated/type/paging';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import RolesList from './RolesList';
 | 
			
		||||
import './RolesList.less';
 | 
			
		||||
 | 
			
		||||
const RolesListPage = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
 | 
			
		||||
  const [roles, setRoles] = useState<Role[]>([]);
 | 
			
		||||
  const [isLoading, setIsLoading] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
  const fetchRoles = async (paging?: Paging) => {
 | 
			
		||||
    setIsLoading(true);
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await getRoles(
 | 
			
		||||
        'policies,teams,users',
 | 
			
		||||
        paging?.after,
 | 
			
		||||
        paging?.before
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      setRoles(data.data || []);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      showErrorToast(error as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleAddRole = () => {
 | 
			
		||||
    history.push(ROUTES.ADD_ROLE);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchRoles();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return isLoading ? (
 | 
			
		||||
    <Loader />
 | 
			
		||||
  ) : (
 | 
			
		||||
    <Row className="roles-list-container" gutter={[16, 16]}>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <Space align="center" className="tw-w-full tw-justify-end" size={16}>
 | 
			
		||||
          <Button type="primary" onClick={handleAddRole}>
 | 
			
		||||
            Add Role
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Space>
 | 
			
		||||
      </Col>
 | 
			
		||||
      <Col span={24}>
 | 
			
		||||
        <RolesList roles={roles} />
 | 
			
		||||
      </Col>
 | 
			
		||||
    </Row>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default RolesListPage;
 | 
			
		||||
@ -13,6 +13,7 @@
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Redirect, Route, Switch } from 'react-router-dom';
 | 
			
		||||
import { ROUTES } from '../constants/constants';
 | 
			
		||||
import {
 | 
			
		||||
  GlobalSettingOptions,
 | 
			
		||||
  GlobalSettingsMenuCategory,
 | 
			
		||||
@ -35,9 +36,28 @@ const CustomPropertiesPageV1 = withSuspenseFallback(
 | 
			
		||||
    () => import('../pages/CustomPropertiesPage/CustomPropertiesPageV1')
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
const RolesPageComponent = withSuspenseFallback(
 | 
			
		||||
  React.lazy(() => import('../pages/RolesPage/RolesPage.component'))
 | 
			
		||||
const RolesListPage = withSuspenseFallback(
 | 
			
		||||
  React.lazy(() => import('../pages/RolesPage/RolesListPage/RolesListPage'))
 | 
			
		||||
);
 | 
			
		||||
const RolesDetailPage = withSuspenseFallback(
 | 
			
		||||
  React.lazy(() => import('../pages/RolesPage/RolesDetailPage/RolesDetailPage'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const AddRolePage = withSuspenseFallback(
 | 
			
		||||
  React.lazy(() => import('../pages/RolesPage/AddRolePage/AddRolePage'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const PoliciesDetailPage = withSuspenseFallback(
 | 
			
		||||
  React.lazy(
 | 
			
		||||
    () => import('../pages/PoliciesPage/PoliciesDetailPage/PoliciesDetailPage')
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
const PoliciesListPage = withSuspenseFallback(
 | 
			
		||||
  React.lazy(
 | 
			
		||||
    () => import('../pages/PoliciesPage/PoliciesListPage/PoliciesListPage')
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const UserListPageV1 = withSuspenseFallback(
 | 
			
		||||
  React.lazy(() => import('../pages/UserListPage/UserListPageV1'))
 | 
			
		||||
);
 | 
			
		||||
@ -70,14 +90,48 @@ const GlobalSettingRouter = () => {
 | 
			
		||||
          true
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
      {/* Roles route start
 | 
			
		||||
       * Do not change the order of these route
 | 
			
		||||
       */}
 | 
			
		||||
      <Route
 | 
			
		||||
        exact
 | 
			
		||||
        component={RolesPageComponent}
 | 
			
		||||
        component={RolesListPage}
 | 
			
		||||
        path={getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.ROLES
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
      <Route exact component={AddRolePage} path={ROUTES.ADD_ROLE} />
 | 
			
		||||
      <Route
 | 
			
		||||
        exact
 | 
			
		||||
        component={RolesDetailPage}
 | 
			
		||||
        path={getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.ROLES,
 | 
			
		||||
          true
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
      {/* Roles route end
 | 
			
		||||
       * Do not change the order of these route
 | 
			
		||||
       */}
 | 
			
		||||
 | 
			
		||||
      <Route
 | 
			
		||||
        exact
 | 
			
		||||
        component={PoliciesListPage}
 | 
			
		||||
        path={getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.POLICIES
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
        exact
 | 
			
		||||
        component={PoliciesDetailPage}
 | 
			
		||||
        path={getSettingPath(
 | 
			
		||||
          GlobalSettingsMenuCategory.ACCESS,
 | 
			
		||||
          GlobalSettingOptions.POLICIES,
 | 
			
		||||
          true
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
        exact
 | 
			
		||||
        component={UserListPageV1}
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,8 @@ import { Pipeline } from '../generated/entity/data/pipeline';
 | 
			
		||||
import { Table } from '../generated/entity/data/table';
 | 
			
		||||
import { Topic } from '../generated/entity/data/topic';
 | 
			
		||||
import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread';
 | 
			
		||||
import { Policy } from '../generated/entity/policies/policy';
 | 
			
		||||
import { Role } from '../generated/entity/teams/role';
 | 
			
		||||
import { Team } from '../generated/entity/teams/team';
 | 
			
		||||
import { EntityReference, User } from '../generated/entity/teams/user';
 | 
			
		||||
import { Paging } from '../generated/type/paging';
 | 
			
		||||
@ -630,6 +632,8 @@ export const getEntityName = (
 | 
			
		||||
    | Table
 | 
			
		||||
    | Pipeline
 | 
			
		||||
    | Team
 | 
			
		||||
    | Policy
 | 
			
		||||
    | Role
 | 
			
		||||
) => {
 | 
			
		||||
  return entity?.displayName || entity?.name || '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -239,3 +239,25 @@ export const getTeamsWithFqnPath = (fqn: string) => {
 | 
			
		||||
 | 
			
		||||
  return path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getRoleWithFqnPath = (fqn: string) => {
 | 
			
		||||
  let path = ROUTES.SETTINGS_WITH_TAB_FQN;
 | 
			
		||||
 | 
			
		||||
  path = path
 | 
			
		||||
    .replace(PLACEHOLDER_SETTING_CATEGORY, GlobalSettingsMenuCategory.ACCESS)
 | 
			
		||||
    .replace(PLACEHOLDER_ROUTE_TAB, GlobalSettingOptions.ROLES)
 | 
			
		||||
    .replace(PLACEHOLDER_ROUTE_FQN, fqn);
 | 
			
		||||
 | 
			
		||||
  return path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getPolicyWithFqnPath = (fqn: string) => {
 | 
			
		||||
  let path = ROUTES.SETTINGS_WITH_TAB_FQN;
 | 
			
		||||
 | 
			
		||||
  path = path
 | 
			
		||||
    .replace(PLACEHOLDER_SETTING_CATEGORY, GlobalSettingsMenuCategory.ACCESS)
 | 
			
		||||
    .replace(PLACEHOLDER_ROUTE_TAB, GlobalSettingOptions.POLICIES)
 | 
			
		||||
    .replace(PLACEHOLDER_ROUTE_FQN, fqn);
 | 
			
		||||
 | 
			
		||||
  return path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -131,6 +131,7 @@ import IconPipelineGrey from '../assets/svg/pipeline-grey.svg';
 | 
			
		||||
import IconPipeline from '../assets/svg/pipeline.svg';
 | 
			
		||||
import IconPlusPrimery from '../assets/svg/plus-primery.svg';
 | 
			
		||||
import IconPlus from '../assets/svg/plus.svg';
 | 
			
		||||
import IconPolicies from '../assets/svg/policies.svg';
 | 
			
		||||
import IconProfilerColor from '../assets/svg/profiler-color.svg';
 | 
			
		||||
import IconProfiler from '../assets/svg/profiler.svg';
 | 
			
		||||
import IconHelpCircle from '../assets/svg/question-circle.svg';
 | 
			
		||||
@ -327,6 +328,7 @@ export const Icons = {
 | 
			
		||||
  TASK_OPEN: 'task-open',
 | 
			
		||||
  FOREGIN_KEY: 'foreign-key',
 | 
			
		||||
  ROLE_GREY: 'role-grey',
 | 
			
		||||
  POLICIES: 'policies',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SVGIcons: FunctionComponent<Props> = ({
 | 
			
		||||
@ -957,6 +959,10 @@ const SVGIcons: FunctionComponent<Props> = ({
 | 
			
		||||
    case Icons.FOREGIN_KEY:
 | 
			
		||||
      IconComponent = IconForeignKey;
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    case Icons.POLICIES:
 | 
			
		||||
      IconComponent = IconPolicies;
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user