github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/pages/repositories/repository/actions/index.jsx (about) 1 import React, {useEffect, useState} from "react"; 2 import { useOutletContext } from "react-router-dom"; 3 import { 4 ActionGroup, 5 ActionsBar, 6 AlertError, 7 FormattedDate, 8 Loading, Na, RefreshButton, 9 TooltipButton 10 } from "../../../../lib/components/controls"; 11 import {useRefs} from "../../../../lib/hooks/repo"; 12 import {useAPIWithPagination} from "../../../../lib/hooks/api"; 13 import {actions} from "../../../../lib/api"; 14 import { 15 FilterIcon, 16 XIcon 17 } from "@primer/octicons-react"; 18 import {Table} from "react-bootstrap"; 19 import {Paginator} from "../../../../lib/components/pagination"; 20 import {ActionStatusIcon} from "../../../../lib/components/repository/actions"; 21 import {Link} from "../../../../lib/components/nav"; 22 import {useRouter} from "../../../../lib/hooks/router"; 23 import Alert from "react-bootstrap/Alert"; 24 import {RepoError} from "../error"; 25 26 27 const RunRow = ({ repo, run, onFilterBranch, onFilterCommit }) => { 28 return ( 29 <tr> 30 <td> 31 <ActionStatusIcon className="me-2" status={run.status}/> 32 {' '} 33 <Link href={{ 34 pathname: '/repositories/:repoId/actions/:runId', 35 params: {repoId: repo.id, runId: run.run_id} 36 }}> 37 {run.run_id} 38 </Link> 39 </td> 40 <td>{run.event_type}</td> 41 <td> 42 <Link className="me-2" href={{ 43 pathname: '/repositories/:repoId/objects', 44 params: {repoId: repo.id}, 45 query: {ref: run.branch} 46 }}> 47 {run.branch} 48 </Link> 49 <TooltipButton 50 onClick={() => onFilterBranch(run.branch)} 51 variant="link" 52 tooltip="filter by branch" 53 className="row-hover" 54 size="sm"> 55 <FilterIcon size="small"/> 56 </TooltipButton> 57 </td> 58 <td><FormattedDate dateValue={run.start_time}/></td> 59 <td> 60 {(!run.end_time) ? <Na/> :<FormattedDate dateValue={run.end_time}/>} 61 </td> 62 <td> 63 {(!run.commit_id) ? <Na/> : ( 64 <> 65 <Link className="me-2" href={{ 66 pathname: '/repositories/:repoId/commits/:commitId', 67 params: {repoId: repo.id, commitId: run.commit_id} 68 }}> 69 <code>{run.commit_id.substr(0, 12)}</code> 70 </Link> 71 <TooltipButton 72 onClick={() => onFilterCommit(run.commit_id)} 73 variant="link" 74 tooltip="filter by commit ID" 75 className="row-hover" 76 size="sm"> 77 <FilterIcon size="small"/> 78 </TooltipButton> 79 </> 80 )} 81 </td> 82 </tr> 83 ) 84 } 85 86 const RunTable = ({ repo, runs, nextPage, after, onPaginate, onFilterBranch, onFilterCommit }) => { 87 return ( 88 <> 89 <Table> 90 <thead> 91 <tr> 92 <th>Run ID</th> 93 <th>Event</th> 94 <th>Branch</th> 95 <th>Start Time</th> 96 <th>End Time</th> 97 <th>Commit ID</th> 98 </tr> 99 </thead> 100 <tbody> 101 {runs.map(run => <RunRow 102 key={run.run_id} 103 repo={repo} 104 run={run} 105 onFilterBranch={onFilterBranch} 106 onFilterCommit={onFilterCommit}/>)} 107 </tbody> 108 </Table> 109 <Paginator onPaginate={onPaginate} after={after} nextPage={nextPage}/> 110 </> 111 ) 112 } 113 114 const ActionsList = ({ repo, after, onPaginate, branch, commit, onFilterBranch, onFilterCommit }) => { 115 116 const [refresh, setRefresh] = useState(false) 117 const {results, loading, error, nextPage} = useAPIWithPagination(async () => { 118 return await actions.listRuns(repo.id, branch, commit, after) 119 }, [repo.id, after, refresh, branch, commit]) 120 121 const doRefresh = () => setRefresh(!refresh) 122 123 let content; 124 if (error) content = <AlertError error={error}/> 125 126 else if (loading) content = <Loading/> 127 else if (results.length === 0 && !nextPage) content = <Alert variant="info" className={"mt-3"}>No action runs have been logged yet.</Alert> 128 else content = ( 129 <RunTable 130 repo={repo} 131 runs={results} 132 nextPage={nextPage} 133 after={after} 134 onPaginate={onPaginate} 135 onFilterCommit={onFilterCommit} 136 onFilterBranch={onFilterBranch} 137 /> 138 ) 139 140 let filters = []; 141 if (branch) { 142 filters = [<TooltipButton key="branch" variant="light" tooltip="remove branch filter" onClick={() => onFilterBranch("")}> 143 <XIcon/> {branch} 144 </TooltipButton>] 145 } 146 if (commit) { 147 filters = [...filters, <TooltipButton key="commit" variant="light" tooltip="remove commit filter" onClick={() => onFilterCommit("")}> 148 <XIcon/> {commit.substr(0, 12)} 149 </TooltipButton> ] 150 } 151 152 return ( 153 <div className="mb-5"> 154 <ActionsBar> 155 <ActionGroup orientation="left"> 156 {filters} 157 </ActionGroup> 158 159 <ActionGroup orientation="right"> 160 <RefreshButton onClick={doRefresh}/> 161 </ActionGroup> 162 </ActionsBar> 163 {content} 164 <div> 165 {/* eslint-disable-next-line react/jsx-no-target-blank */} 166 Actions can be configured to run when predefined events occur. <a href="https://docs.lakefs.io/howto/hooks/" target="_blank">Learn more.</a> 167 </div> 168 </div> 169 ) 170 } 171 172 173 const ActionsContainer = () => { 174 const router = useRouter(); 175 const { after } = router.query; 176 const commit = (router.query.commit) ? router.query.commit : ""; 177 const branch = (router.query.branch) ? router.query.branch : ""; 178 179 const { repo, loading, error } = useRefs(); 180 181 if (loading) return <Loading/>; 182 if (error) return <RepoError error={error}/>; 183 184 const params = {repoId: repo.id}; 185 186 return ( 187 <ActionsList 188 repo={repo} 189 after={after} 190 onPaginate={after => { 191 const query = {after}; 192 if (commit) query.commit = commit; 193 if (branch) query.branch = branch; 194 router.push({pathname: `/repositories/:repoId/actions`, query, params}) 195 }} 196 branch={branch} 197 commit={commit} 198 onFilterBranch={branch => { 199 const query = {}; // will reset pagination 200 if (branch) query.branch = branch; 201 router.push({pathname: `/repositories/:repoId/actions`, query, params}) 202 }} 203 onFilterCommit={commit => { 204 const query = {} // will reset pagination 205 if (commit) query.commit = commit; 206 router.push({pathname: `/repositories/:repoId/actions`, query, params}) 207 }} 208 /> 209 ); 210 }; 211 212 export const RepositoryActionsPage = () => { 213 const [setActivePage] = useOutletContext(); 214 useEffect(() => setActivePage("actions"), [setActivePage]); 215 return <ActionsContainer/>; 216 }; 217 218 export default RepositoryActionsPage;