Source

PresentationalComponents/Tailorings/components/TailoringTab.js

import React, { useMemo } from 'react';
import propTypes from 'prop-types';
import { Grid } from '@patternfly/react-core';
import TableStateProvider from '@/Frameworks/AsyncTableTools/components/TableStateProvider';
import { useFullTableState } from '@/Frameworks/AsyncTableTools/hooks/useTableState';
import { RulesTable } from 'PresentationalComponents';
import useTailoringsData from '../hooks/useTailoringsData';
import useSecurityGuideData from '../hooks/useSecurityGuideData';
import useSecurityGuideProfileData from '../hooks/useSecurityGuideProfileData';
import { prepareTreeTable, prepareRules, skips } from '../helpers';
import TabHeader from './TabHeader';
import SecurityGuideRulesToggle from './SecurityGuideRulesToggle';

/**
 * This component is used to show either the tailorings with rules of a specific policy,
 * or the rules of a specific security guide and it's rules for a set of minor OS versions
 *
 *  @param   {object}             [props]                                React component props
 *  @param   {object}             [props.policy]                         A policy object from the API
 *  @param   {string}             [props.policy.id]                      The id used to fetch
 *  @param   {object}             [props.tailoring]                      A tailoring object from the API
 *  @param   {string}             [props.tailoring.id]                   A tailorings ID used to fetch the associated rules, rule groups, and value definitions for
 *  @param   {string}             [props.securityGuideId]                The ID for a security guide that the profile should be queried with
 *  @param   {string}             [props.profileId]                      The ID for a specific profile the rules should fetched for
 *  @param   {string}             [props.osMajorVersion]                 A specific major OS version the profile should be queried with
 *  @param   {string}             [props.osMinorVersion]                 A specific minor OS versions that the profile should be for
 *  @param   {Array}              [props.columns]                        A set of RulesTable columns the table should show
 *  @param   {boolean}            [props.enableSecurityGuideRulesToggle] This enabled the "Selected Only" toggle to appear and allows fetching the security guide rule set for the matching profile
 *  @param                        [props.systemCount]
 *  @param                        [props.rulesPageLink]
 *  @param   {object}             props.rulesTableProps                  React component props to be passed on to the RulesTable component in the tab
 *  @param                        props.resetLink
 *  @param   {Function}           [props.setRuleValues]                  A callback called when a custom rule value is saved
 *  @param   {Function}           [props.onRuleValueReset]               A callback called when values for a rule are reset
 *  @param   {Function}           [props.onValueOverrideSave]            **deprecated** We should be using setRuleValues instead
 *  @param   {Function}           [props.onSelect]                       A callback called when a selection is made
 *  @param   {object}             [props.selected]                       An array of currently selected IDs
 *  @param   {object}             [props.preselected]                    An array of rule IDs to select
 *  @param   {string}             [props.skipProfile]
 *  @param                        [props.additionalRules]
 *  @param                        [props.valueOverrides]
 *  @param                        [props.showResetButton]                Enables reset rules button
 *
 *  @returns {React.ReactElement}
 *
 *  @category Compliance
 *  @tutorial how-to-use-tailorings
 *
 */
const TailoringTab = ({
  policy,
  tailoring,
  securityGuideId: securityGuideIdProp,
  osMajorVersion,
  osMinorVersion,
  profileId,
  columns,
  systemCount,
  rulesTableProps,
  resetLink,
  rulesPageLink,
  setRuleValues,
  onRuleValueReset,
  onValueOverrideSave,
  onSelect,
  selected,
  preselected,
  additionalRules,
  enableSecurityGuideRulesToggle,
  skipProfile,
  valueOverrides,
  showResetButton = false,
}) => {
  const tableState = useFullTableState();
  const openRuleGroups = tableState?.tableState?.['open-items'];
  const groupFilter = useMemo(() => {
    return tableState?.tableState?.tableView === 'tree' &&
      openRuleGroups?.length > 0
      ? `rule_group_id ^ (${openRuleGroups.map((id) => `${id}`).join(' ')})`
      : undefined;
  }, [tableState, openRuleGroups]);

  const securityGuideId = securityGuideIdProp || tailoring?.security_guide_id;
  const shouldSkip = skips({
    skipProfile,
    policy,
    tailoring,
    securityGuideId,
    profileId,
    tableState,
  });

  const {
    data: {
      securityGuide,
      ruleGroups,
      ruleTree: securityGuideRuleTree,
      rules: securityGuideRules,
      valueDefinitions,
    },
    fetchAllIds: fetchAllSecurityGuideRuleIds,
    exporter: securityGuideRulesExporter,
  } = useSecurityGuideData({
    securityGuideId,
    profileId,
    ...shouldSkip.securityGuide,
    ...(groupFilter ? { groupFilter } : {}),
    tableState,
  });

  const {
    data: { rules: profileRules, ruleTree: profileRuleTree },
  } = useSecurityGuideProfileData({
    securityGuideId,
    profileId,
    groupFilter,
    tableState,
    ...shouldSkip.profile,
  });

  const {
    data: { ruleTree: tailoringRuleTree, rules: tailoringRules },
    fetchAllIds: fetchAllTailoringRuleIds,
    exporter: tailoringRulesExporter,
  } = useTailoringsData({
    policy,
    tailoring,
    tableState,
    ...shouldSkip.tailoring,
    ...(groupFilter ? { groupFilter } : {}),
  });

  const rules = useMemo(
    () =>
      prepareRules({
        shouldSkip,
        securityGuideRules,
        profileRules,
        tailoringRules,
        valueDefinitions,
        valueOverrides: { ...tailoring?.value_overrides, ...valueOverrides },
        ...(tableState?.tableState?.selectedRulesOnly ? { selected } : {}),
      }),
    [
      shouldSkip,
      tailoring,
      tailoringRules,
      securityGuideRules,
      profileRules,
      valueDefinitions,
      valueOverrides,
      selected,
      tableState,
    ],
  );

  const ruleTree = useMemo(
    () =>
      prepareTreeTable({
        shouldSkip,
        securityGuideRuleTree,
        profileRuleTree,
        tailoringRuleTree,
        additionalRules,
        ruleGroups,
      }),
    [
      shouldSkip,
      tailoringRuleTree,
      additionalRules,
      ruleGroups,
      securityGuideRuleTree,
      profileRuleTree,
    ],
  );

  // TODO we might want to consider making this more explicit and also add SSG profile exporter and ids call
  const exporter = async () =>
    tailoring && policy
      ? await tailoringRulesExporter()
      : await securityGuideRulesExporter();

  const itemIdsInTable = async () =>
    tailoring && policy
      ? await fetchAllTailoringRuleIds()
      : await fetchAllSecurityGuideRuleIds();

  const onValueSave = (_policyId, ...valueParams) =>
    onValueOverrideSave(tailoring || osMinorVersion, ...valueParams);

  const onSelectRule = (...ruleParams) =>
    onSelect?.(
      tailoring || { ...securityGuide?.data, os_minor_version: osMinorVersion },
      ...ruleParams,
    );

  return (
    <>
      <Grid>
        {(tailoring || securityGuide) && (
          <TabHeader
            tailoring={tailoring}
            securityGuide={
              securityGuide && {
                ...securityGuide.data,
                osMajorVersion,
                osMinorVersion,
              }
            }
            profileId={profileId || tailoring.profile_id}
            rulesPageLink={rulesPageLink}
            showResetButton={showResetButton}
            resetLink={resetLink}
            systemCount={systemCount}
          />
        )}
      </Grid>
      <RulesTable
        policyId={policy?.id}
        securityGuideId={tailoring?.security_guide_id || securityGuideId}
        total={rules?.meta?.total}
        ruleTree={ruleTree}
        rules={ruleTree ? rules?.data || [] : rules?.data}
        ansibleSupportFilter
        remediationsEnabled={false}
        columns={columns}
        setRuleValues={setRuleValues}
        onRuleValueReset={onRuleValueReset}
        onValueOverrideSave={onValueSave}
        onSelect={onSelect ? onSelectRule : undefined}
        selectedRules={selected}
        preselected={preselected}
        options={{
          exporter,
          itemIdsInTable,
        }}
        {...rulesTableProps}
        {...(enableSecurityGuideRulesToggle
          ? {
              DedicatedAction: SecurityGuideRulesToggle,
            }
          : {})}
      />
    </>
  );
};

TailoringTab.propTypes = {
  policy: propTypes.shape({
    id: propTypes.string,
  }),
  tailoring: propTypes.shape({
    id: propTypes.string,
    profile_id: propTypes.string,
    security_guide_id: propTypes.string,
    value_overrides: propTypes.array,
  }),
  securityGuideId: propTypes.string,
  profileId: propTypes.string,
  osMajorVersion: propTypes.string,
  osMinorVersion: propTypes.string,
  valueOverrides: propTypes.object,
  columns: propTypes.array,
  onSelect: propTypes.func,
  systemCount: propTypes.number,
  rulesTableProps: propTypes.object,
  resetLink: propTypes.bool,
  rulesPageLink: propTypes.bool,
  setRuleValues: propTypes.func,
  onRuleValueReset: propTypes.func,
  onValueOverrideSave: propTypes.func,
  showResetButton: propTypes.bool,
  selected: propTypes.array,
  preselected: propTypes.array,
  enableSecurityGuideRulesToggle: propTypes.bool,
  skipProfile: propTypes.string,
  additionalRules: propTypes.object,
};

const TailoringTabProvider = (props) => (
  <TableStateProvider>
    <TailoringTab {...props} />
  </TableStateProvider>
);

export default TailoringTabProvider;