go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/gitiles/components/commit_table/commit_table_row.tsx (about)

     1  // Copyright 2023 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 { ChevronRight, ExpandMore } from '@mui/icons-material';
    16  import { IconButton, TableCell, TableRow, styled } from '@mui/material';
    17  import markdownIt from 'markdown-it';
    18  import { ReactNode, useEffect, useMemo, useState } from 'react';
    19  
    20  import { SanitizedHtml } from '@/common/components/sanitized_html';
    21  import { bugLine } from '@/common/tools/markdown/plugins/bug_line';
    22  import { bugnizerLink } from '@/common/tools/markdown/plugins/bugnizer_link';
    23  import { crbugLink } from '@/common/tools/markdown/plugins/crbug_link';
    24  import { defaultTarget } from '@/common/tools/markdown/plugins/default_target';
    25  import { reviewerLine } from '@/common/tools/markdown/plugins/reviewer_line';
    26  import { OutputCommit } from '@/gitiles/types';
    27  
    28  import { CommitProvider, useDefaultExpandedState } from './context';
    29  
    30  const md = markdownIt('zero', { breaks: true, linkify: true })
    31    .enable(['linkify', 'newline'])
    32    .use(bugLine)
    33    .use(reviewerLine)
    34    .use(crbugLink)
    35    .use(bugnizerLink)
    36    .use(defaultTarget, '_blank');
    37  
    38  const SummaryContainer = styled(SanitizedHtml)({
    39    '& > p:first-of-type': {
    40      marginBlockStart: 0,
    41    },
    42    '& > p:last-of-type': {
    43      marginBlockEnd: 0,
    44    },
    45  });
    46  
    47  export interface CommitTableRowProps {
    48    readonly commit: OutputCommit;
    49    readonly children: ReactNode;
    50  }
    51  
    52  export function CommitTableRow({ commit, children }: CommitTableRowProps) {
    53    const [defaultExpanded] = useDefaultExpandedState();
    54    const [expanded, setExpanded] = useState(() => defaultExpanded);
    55    useEffect(() => {
    56      setExpanded(defaultExpanded);
    57    }, [defaultExpanded]);
    58  
    59    const { descriptionHtml, changedFiles } = useMemo(
    60      () => ({
    61        descriptionHtml: md.render(commit.message),
    62        changedFiles: commit.treeDiff.map((diff) =>
    63          // If a file was moved, there is both an old and a new path, from which
    64          // we take only the new path.
    65          // If a file was deleted, its new path is /dev/null. In that case, we're
    66          // only interested in the old path.
    67          !diff.newPath || diff.newPath === '/dev/null'
    68            ? diff.oldPath
    69            : diff.newPath,
    70        ),
    71      }),
    72      [commit],
    73    );
    74  
    75    return (
    76      <>
    77        <TableRow
    78          sx={{
    79            '& > td': {
    80              whiteSpace: 'nowrap',
    81            },
    82          }}
    83        >
    84          <TableCell>
    85            <IconButton
    86              aria-label="toggle-row"
    87              size="small"
    88              onClick={() => setExpanded(!expanded)}
    89            >
    90              {expanded ? <ExpandMore /> : <ChevronRight />}
    91            </IconButton>
    92          </TableCell>
    93          {/* Pass commit to cells via context so composing a row require less
    94           ** boilerplate. */}
    95          <CommitProvider value={commit}>{children}</CommitProvider>
    96        </TableRow>
    97        {/* Always render the content row to DOM to ensure a stable DOM structure.
    98         **/}
    99        <TableRow
   100          data-testid="content-row"
   101          sx={{ display: expanded ? '' : 'none' }}
   102        >
   103          <TableCell colSpan={100} sx={{ 'tr > &': { padding: 0 } }}>
   104            <div css={{ padding: '10px 20px' }}>
   105              <SummaryContainer html={descriptionHtml} />
   106              <h4 css={{ marginBlockEnd: '0px' }}>
   107                Changed files: {changedFiles.length}
   108              </h4>
   109              <ul>
   110                {changedFiles.map((filename, i) => (
   111                  <li key={i}>{filename}</li>
   112                ))}
   113              </ul>
   114            </div>
   115          </TableCell>
   116        </TableRow>
   117      </>
   118    );
   119  }