go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/frontend/ui/src/components/rules_table/rules_table.tsx (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import Box from '@mui/material/Box';
    16  import LinearProgress from '@mui/material/LinearProgress';
    17  import Table from '@mui/material/Table';
    18  import TableBody from '@mui/material/TableBody';
    19  import TableCell from '@mui/material/TableCell';
    20  import TableContainer from '@mui/material/TableContainer';
    21  import TableHead from '@mui/material/TableHead';
    22  import TableRow from '@mui/material/TableRow';
    23  import FormControl from '@mui/material/FormControl';
    24  import InputLabel from '@mui/material/InputLabel';
    25  import MenuItem from '@mui/material/MenuItem';
    26  import Select, { SelectChangeEvent } from '@mui/material/Select';
    27  
    28  import LoadErrorAlert from '@/components/load_error_alert/load_error_alert';
    29  import { useFetchProjectConfig } from '@/hooks/use_fetch_project_config';
    30  import useFetchRules from '@/hooks/use_fetch_rules';
    31  
    32  import { useProblemFilterParam } from './hooks';
    33  import RuleRow from './rule_row/rule_row';
    34  import ProblemChip from './problem_chip/problem_chip';
    35  
    36  interface Props {
    37    project: string;
    38  }
    39  
    40  const RulesTable = ({ project } : Props ) => {
    41    const {
    42      isLoading: isConfigLoading,
    43      data: projectConfig,
    44      error: configError,
    45    } = useFetchProjectConfig(project);
    46  
    47    const { isLoading, data: rules, error } = useFetchRules(project);
    48  
    49    const policyIDs = projectConfig?.bugManagement?.policies?.map((p) => p.id) || [];
    50    const [problemFilter, setProblemFilter] = useProblemFilterParam(policyIDs);
    51  
    52    if (error) {
    53      return <LoadErrorAlert
    54        entityName='rules'
    55        error={error}
    56      />;
    57    }
    58    if (configError) {
    59      return <LoadErrorAlert
    60        entityName='project config'
    61        error={configError}
    62      />;
    63    }
    64    if (isLoading || isConfigLoading) {
    65      return <LinearProgress />;
    66    }
    67  
    68  
    69    const handleProblemFilterChange = (event: SelectChangeEvent<string>) => {
    70      const value = event.target.value;
    71      setProblemFilter(value, true);
    72    };
    73  
    74    const colorIndexFunc = (policyID: string): number => {
    75      return policyIDs.indexOf(policyID);
    76    };
    77  
    78    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    79    const filteredRules = (rules || []).filter((r) => problemFilter == '' || r.bugManagementState!.policyState?.some((ps) => ps.policyId == problemFilter && ps.lastActivationTime));
    80  
    81    return (
    82      <>
    83        <Box sx={{ paddingTop: '8px', paddingBottom: '12px' }}>
    84          <FormControl fullWidth data-testid="problem_filter">
    85            <InputLabel id="problem_filter_label">Problem filter</InputLabel>
    86            <Select
    87              labelId="problem_filter_label"
    88              id="problem_filter"
    89              label="Problem filter"
    90              value={problemFilter}
    91              onChange={handleProblemFilterChange}
    92              inputProps={{ 'data-testid': 'problem_filter_input' }}>
    93              <MenuItem value="">All rules</MenuItem>
    94              {projectConfig?.bugManagement?.policies?.map((policy) => (
    95                <MenuItem
    96                  key={policy.id}
    97                  value={policy.id}>
    98                  Only rules with problem&nbsp;
    99                  <ProblemChip
   100                    key={policy.id}
   101                    policy={policy}
   102                    active
   103                    colorIndex={colorIndexFunc(policy.id)}
   104                  />&nbsp;/&nbsp;{policy.humanReadableName}
   105                </MenuItem>
   106              ))}
   107            </Select>
   108          </FormControl>
   109        </Box>
   110        <TableContainer component={Box}>
   111          <Table data-testid="impact-table" size="small" sx={{ overflowWrap: 'anywhere' }}>
   112            <TableHead>
   113              <TableRow>
   114                <TableCell>Rule Definition</TableCell>
   115                <TableCell>Problems</TableCell>
   116                <TableCell sx={{ width: '180px' }}>Bug</TableCell>
   117                <TableCell sx={{ width: '100px' }}>Last Updated</TableCell>
   118              </TableRow>
   119            </TableHead>
   120            <TableBody>
   121              {
   122                filteredRules.map((rule) => (
   123                  <RuleRow
   124                    key={rule.ruleId}
   125                    rule={rule}
   126                    bugManagementConfig={projectConfig?.bugManagement}
   127                    focusPolicyID={problemFilter}
   128                    colorIndexFunc={colorIndexFunc} />
   129                ))
   130              }
   131            </TableBody>
   132          </Table>
   133        </TableContainer>
   134      </>
   135    );
   136  };
   137  
   138  export default RulesTable;