go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/frontend/ui/src/components/bug_picker/bug_picker.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 { ChangeEvent } from 'react';
    16  import { useParams } from 'react-router-dom';
    17  
    18  import CircularProgress from '@mui/material/CircularProgress';
    19  import FormControl from '@mui/material/FormControl';
    20  import Grid from '@mui/material/Grid';
    21  import InputLabel from '@mui/material/InputLabel';
    22  import MenuItem from '@mui/material/MenuItem';
    23  import Select, { SelectChangeEvent } from '@mui/material/Select';
    24  import TextField from '@mui/material/TextField';
    25  
    26  import ErrorAlert from '@/components/error_alert/error_alert';
    27  import LoadErrorAlert from '@/components/load_error_alert/load_error_alert';
    28  import { useFetchProjectConfig } from '@/hooks/use_fetch_project_config';
    29  
    30  interface Props {
    31      bugSystem: string;
    32      bugId: string;
    33      handleBugSystemChanged: (bugSystem: string) => void;
    34      handleBugIdChanged: (bugId: string) => void;
    35  }
    36  
    37  const getMonorailSystem = (bugId: string): string | null => {
    38    if (bugId.indexOf('/') >= 0) {
    39      const parts = bugId.split('/');
    40      return parts[0];
    41    } else {
    42      return null;
    43    }
    44  };
    45  
    46  const getBugNumber = (bugId: string): string => {
    47    if (bugId.indexOf('/') >= 0) {
    48      const parts = bugId.split('/');
    49      return parts[1];
    50    } else {
    51      return bugId;
    52    }
    53  };
    54  
    55  /**
    56   * An enum representing the supported bug systems.
    57   *
    58   * This is needed because mui's <Select> doesn't compare string correctly in typescript.
    59   */
    60  enum BugSystems {
    61      EMPTY = '',
    62      MONORAIL = 'monorail',
    63      BUGANIZER = 'buganizer',
    64  }
    65  
    66  /**
    67   * This method works around the fact that Select
    68   * components compare strings for reference.
    69   *
    70   * @param {string} bugSystem The bug system to find in the enum.
    71   * @return {string} A static enum value equal to the string
    72   *          provided and used in the Select component.
    73   */
    74  const getStaticBugSystem = (bugSystem: string): string => {
    75    switch (bugSystem) {
    76      case '': {
    77        return BugSystems.EMPTY;
    78      }
    79      case 'monorail': {
    80        return BugSystems.MONORAIL;
    81      }
    82      case 'buganizer': {
    83        return BugSystems.BUGANIZER;
    84      }
    85      default: {
    86        throw new Error('Unknown bug system.');
    87      }
    88    }
    89  };
    90  
    91  const BugPicker = ({
    92    bugSystem,
    93    bugId,
    94    handleBugSystemChanged,
    95    handleBugIdChanged,
    96  }: Props) => {
    97    const { project } = useParams();
    98  
    99    const {
   100      isLoading,
   101      data: projectConfig,
   102      error,
   103    } = useFetchProjectConfig(project || '');
   104  
   105    if (!project) {
   106      return (
   107        <ErrorAlert
   108          showError
   109          errorTitle="Project not defined"
   110          errorText={'No project param detected.}'}/>
   111      );
   112    }
   113  
   114    const selectedBugSystem = getStaticBugSystem(bugSystem);
   115  
   116    if (isLoading || !projectConfig) {
   117      return (
   118        <Grid container justifyContent="center">
   119          <CircularProgress data-testid="circle-loading" />
   120        </Grid>
   121      );
   122    }
   123  
   124    if (error) {
   125      return <LoadErrorAlert
   126        entityName="project config"
   127        error={error}
   128      />;
   129    }
   130  
   131    const monorailSystem = getMonorailSystem(bugId);
   132  
   133    const onBugSystemChange = (e: SelectChangeEvent<typeof bugSystem>) => {
   134      handleBugSystemChanged(e.target.value);
   135  
   136      // When the bug system changes, we also need to update the Bug ID.
   137      if (e.target.value == 'monorail') {
   138        // Switching to monorail is disabled if the project config does not have
   139        // monorail details, so we can assume it exists.
   140        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
   141        const monorailConfig = projectConfig.bugManagement!.monorail!;
   142        handleBugIdChanged(`${monorailConfig.project}/${getBugNumber(bugId)}`);
   143      } else if (e.target.value == 'buganizer') {
   144        handleBugIdChanged(getBugNumber(bugId));
   145      }
   146    };
   147  
   148    const onBugNumberChange = (e: ChangeEvent<HTMLInputElement>) => {
   149      const enteredBugId = e.target.value;
   150  
   151      if (monorailSystem != null) {
   152        handleBugIdChanged(`${monorailSystem}/${enteredBugId}`);
   153      } else {
   154        handleBugIdChanged(enteredBugId);
   155      }
   156    };
   157  
   158    return (
   159      <Grid container item columnSpacing={1}>
   160        <Grid item xs={6}>
   161          <FormControl variant="standard" fullWidth>
   162            <InputLabel id="bug-picker_select-bug-tracker-label">Bug tracker</InputLabel>
   163            <Select
   164              labelId="bug-picker_select-bug-tracker-label"
   165              id="bug-picker_select-bug-tracker"
   166              value={selectedBugSystem}
   167              onChange={onBugSystemChange}
   168              variant="standard"
   169              inputProps={{ 'data-testid': 'bug-system' }}>
   170              <MenuItem value={getStaticBugSystem('monorail')} disabled={!projectConfig.bugManagement?.monorail}>
   171                {projectConfig.bugManagement?.monorail?.displayPrefix || 'monorail'}
   172              </MenuItem>
   173              <MenuItem value={getStaticBugSystem('buganizer')}>
   174                  Buganizer
   175              </MenuItem>
   176            </Select>
   177          </FormControl>
   178        </Grid>
   179        <Grid item xs={6}>
   180          <TextField
   181            label="Bug number"
   182            variant="standard"
   183            inputProps={{ 'data-testid': 'bug-number' }}
   184            value={getBugNumber(bugId)}
   185            onChange={onBugNumberChange}/>
   186        </Grid>
   187      </Grid>
   188    );
   189  };
   190  
   191  export default BugPicker;