diff --git a/engine/internal/provision/thinclones/zfs/branching.go b/engine/internal/provision/thinclones/zfs/branching.go index 601fa095de76e21f8ecfa1df62cf868ce2f1fb0e..67893a826b2638498c6dcda3bceb60301da1d62b 100644 --- a/engine/internal/provision/thinclones/zfs/branching.go +++ b/engine/internal/provision/thinclones/zfs/branching.go @@ -267,13 +267,15 @@ func (m *Manager) ListAllBranches(poolList []string) ([]models.BranchEntity, err continue } + dataset := branching.ParseBaseDatasetFromSnapshot(fields[1]) + if !strings.Contains(fields[0], branchSep) { - branches = append(branches, models.BranchEntity{Name: fields[0], SnapshotID: fields[1]}) + branches = append(branches, models.BranchEntity{Name: fields[0], Dataset: dataset, SnapshotID: fields[1]}) continue } for _, branchName := range strings.Split(fields[0], branchSep) { - branches = append(branches, models.BranchEntity{Name: branchName, SnapshotID: fields[1]}) + branches = append(branches, models.BranchEntity{Name: branchName, Dataset: dataset, SnapshotID: fields[1]}) } } diff --git a/engine/internal/srv/branch.go b/engine/internal/srv/branch.go index 389b931cd89a8b30f07f7562d03c53ef724fe532..6b5567fb45f3fee9474a9e03c5bf3204565525e6 100644 --- a/engine/internal/srv/branch.go +++ b/engine/internal/srv/branch.go @@ -47,9 +47,6 @@ func (s *Server) listBranches(w http.ResponseWriter, r *http.Request) { branchDetails := make([]models.BranchView, 0, len(branches)) - // branchRegistry is used to display the "main" branch with only the most recent snapshot. - branchRegistry := make(map[string]int, 0) - for _, branchEntity := range branches { snapshotDetails, ok := repo.Snapshots[branchEntity.SnapshotID] if !ok { @@ -60,6 +57,7 @@ func (s *Server) listBranches(w http.ResponseWriter, r *http.Request) { branchView := models.BranchView{ Name: branchEntity.Name, + BaseDataset: branchEntity.Dataset, Parent: parentSnapshot, DataStateAt: snapshotDetails.DataStateAt, SnapshotID: snapshotDetails.ID, @@ -67,15 +65,6 @@ func (s *Server) listBranches(w http.ResponseWriter, r *http.Request) { NumSnapshots: numSnapshots, } - if position, ok := branchRegistry[branchEntity.Name]; ok { - if branchView.DataStateAt > branchDetails[position].DataStateAt { - branchDetails[position] = branchView - } - - continue - } - - branchRegistry[branchView.Name] = len(branchDetails) branchDetails = append(branchDetails, branchView) } @@ -136,15 +125,36 @@ func containsString(slice []string, s string) bool { } func (s *Server) getFSManagerForBranch(branchName string) (pool.FSManager, error) { + return s.getFSManagerForBranchAndDataset(branchName, "") +} + +func (s *Server) getFSManagerForBranchAndDataset(branchName, dataset string) (pool.FSManager, error) { allBranches, err := s.getAllAvailableBranches(s.pm.First()) if err != nil { return nil, fmt.Errorf("failed to get branch list: %w", err) } for _, branchEntity := range allBranches { - if branchEntity.Name == branchName { // TODO: filter by pool name as well because branch name is ambiguous. + if branchEntity.Name != branchName { + continue + } + + if dataset == "" { return s.getFSManagerForSnapshot(branchEntity.SnapshotID) } + + fsm, err := s.getFSManagerForSnapshot(branchEntity.SnapshotID) + if err != nil { + continue + } + + if fsm.Pool().Name == dataset { + return fsm, nil + } + } + + if dataset != "" { + return nil, fmt.Errorf("failed to find dataset %s of the branch: %s", dataset, branchName) } return nil, fmt.Errorf("failed to found dataset of the branch: %s", branchName) @@ -472,6 +482,18 @@ func filterSnapshotsByBranch(pool *resources.Pool, branch string, snapshots []mo return filtered } +func filterSnapshotsByDataset(dataset string, snapshots []models.Snapshot) []models.Snapshot { + filtered := make([]models.Snapshot, 0) + + for _, sn := range snapshots { + if sn.Pool == dataset { + filtered = append(filtered, sn) + } + } + + return filtered +} + func (s *Server) log(w http.ResponseWriter, r *http.Request) { branchName := mux.Vars(r)["branchName"] diff --git a/engine/internal/srv/routes.go b/engine/internal/srv/routes.go index 470634ab3ff233e883b57da509d160860b4ce0ef..6292330c9cdd00b3021c9f46faf67f6fc9676d73 100644 --- a/engine/internal/srv/routes.go +++ b/engine/internal/srv/routes.go @@ -108,8 +108,11 @@ func (s *Server) getSnapshots(w http.ResponseWriter, r *http.Request) { return } - if branchRequest := r.URL.Query().Get("branch"); branchRequest != "" { - fsm, err := s.getFSManagerForBranch(branchRequest) + branchRequest := r.URL.Query().Get("branch") + datasetRequest := r.URL.Query().Get("dataset") + + if branchRequest != "" { + fsm, err := s.getFSManagerForBranchAndDataset(branchRequest, datasetRequest) if err != nil { api.SendBadRequestError(w, r, err.Error()) return @@ -123,6 +126,10 @@ func (s *Server) getSnapshots(w http.ResponseWriter, r *http.Request) { snapshots = filterSnapshotsByBranch(fsm.Pool(), branchRequest, snapshots) } + if branchRequest == "" && datasetRequest != "" { + snapshots = filterSnapshotsByDataset(datasetRequest, snapshots) + } + if err = api.WriteJSON(w, http.StatusOK, snapshots); err != nil { api.SendError(w, r, err) return diff --git a/engine/pkg/models/branch.go b/engine/pkg/models/branch.go index e29f3cc75f43f90cd088a21bc7dc0aa177209bfe..dcdf420385f5ec52e44cb5dfe280b18e2c6cb74c 100644 --- a/engine/pkg/models/branch.go +++ b/engine/pkg/models/branch.go @@ -35,6 +35,7 @@ type SnapshotDetails struct { // BranchView describes branch view. type BranchView struct { Name string `json:"name"` + BaseDataset string `json:"baseDataset"` Parent string `json:"parent"` DataStateAt string `json:"dataStateAt"` SnapshotID string `json:"snapshotID"` @@ -45,5 +46,6 @@ type BranchView struct { // BranchEntity defines a branch-snapshot pair. type BranchEntity struct { Name string + Dataset string SnapshotID string } diff --git a/engine/pkg/util/branching/branching.go b/engine/pkg/util/branching/branching.go index 75053856cbfe80b4f4e9b8170764b795c01eb679..6159bce1d268a9970a67e4f098e718f7b70824a6 100644 --- a/engine/pkg/util/branching/branching.go +++ b/engine/pkg/util/branching/branching.go @@ -108,3 +108,15 @@ func ParseBranchNameFromSnapshot(snapshot, poolName string) string { return branch } + +// ParseBaseDatasetFromSnapshot parses base dataset from the snapshot ID. +func ParseBaseDatasetFromSnapshot(snapshot string) string { + fullDataset, _, found := strings.Cut(snapshot, "@") + if !found { + return "" + } + + dataset, _, _ := strings.Cut(fullDataset, "/"+BranchDir+"/") + + return dataset +} diff --git a/ui/packages/ce/package.json b/ui/packages/ce/package.json index 098bfbe4ea91f3effbe8b09e7f034ca857ead6fd..00d73fec354463432c39e8ab117abcd96d49a0b1 100644 --- a/ui/packages/ce/package.json +++ b/ui/packages/ce/package.json @@ -1,6 +1,6 @@ { "name": "@postgres.ai/ce", - "version": "4.0.1", + "version": "4.0.2", "private": true, "dependencies": { "@craco/craco": "^6.4.3", diff --git a/ui/packages/ce/src/api/snapshots/getSnapshots.ts b/ui/packages/ce/src/api/snapshots/getSnapshots.ts index b26788ebda5df598bcee5650f355a1c29b7deb68..16af68a8b2f8d4393594f1b9bd8623d027ba788d 100644 --- a/ui/packages/ce/src/api/snapshots/getSnapshots.ts +++ b/ui/packages/ce/src/api/snapshots/getSnapshots.ts @@ -13,7 +13,14 @@ import { import { request } from 'helpers/request' export const getSnapshots: GetSnapshots = async (req) => { - const url = `/snapshots${req.branchName ? `?branch=${req.branchName}` : ''}`; + const params = new URLSearchParams() + if (req.branchName) { + params.append('branch', req.branchName) + } + if (req.dataset) { + params.append('dataset', req.dataset) + } + const url = `/snapshots${params.toString() ? `?${params.toString()}` : ''}`; const response = await request(url); return { diff --git a/ui/packages/shared/package.json b/ui/packages/shared/package.json index 21743956d6ddca78283f46ac31f6543b145ded79..7efcdbb2e8c4731e85f51c7dc4ad031d719634a0 100644 --- a/ui/packages/shared/package.json +++ b/ui/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@postgres.ai/shared", - "version": "4.0.1", + "version": "4.0.2", "scripts": { "build": "tsc -p tsconfig.build.json && node scripts/copy-assets.js", "pack": "node scripts/pack.js" diff --git a/ui/packages/shared/pages/CreateBranch/index.tsx b/ui/packages/shared/pages/CreateBranch/index.tsx index 0ae113547e710e634a6c65dbccb51ea33369fdef..d828d1d9eb7cda9591679c49b84e8a057e1fe76c 100644 --- a/ui/packages/shared/pages/CreateBranch/index.tsx +++ b/ui/packages/shared/pages/CreateBranch/index.tsx @@ -107,6 +107,7 @@ export const CreateBranchPage = observer( const classes = useStyles() const history = useHistory() const [branchSnapshots, setBranchSnapshots] = useState([]) + const [selectedBranchKey, setSelectedBranchKey] = useState('main|') const { load, @@ -131,8 +132,8 @@ export const CreateBranchPage = observer( }) } - const fetchSnapshots = async (branchName: string) => { - await getSnapshots(instanceId, branchName).then((response) => { + const fetchSnapshots = async (branchName: string, dataset?: string) => { + await getSnapshots(instanceId, branchName, dataset).then((response) => { if (response) { setBranchSnapshots(response) formik.setFieldValue('snapshotID', response[0]?.id) @@ -143,17 +144,28 @@ export const CreateBranchPage = observer( const handleParentBranchChange = async ( e: React.ChangeEvent, ) => { - const branchName = e.target.value + const compositeKey = e.target.value + const [branchName, dataset] = compositeKey.split('|') + + setSelectedBranchKey(compositeKey) formik.setFieldValue('baseBranch', branchName) - await fetchSnapshots(branchName) + await fetchSnapshots(branchName, dataset) } const [{ formik }] = useForm(handleSubmit) - useEffect(() => { - load(instanceId) - fetchSnapshots(formik.values.baseBranch) - }, [formik.values.baseBranch]) + useEffect(() => { + load(instanceId); + }, [instanceId]); + + useEffect(() => { + if (!branchesList?.length) return; + const selected = branchesList.find(b => b.name === formik.values.baseBranch); + if (!selected) return; + const compositeKey = `${selected.name}|${selected.baseDataset || ''}`; + setSelectedBranchKey(compositeKey); + fetchSnapshots(selected.name, selected.baseDataset); + }, [branchesList]); if (isBranchesLoading) { return @@ -207,16 +219,20 @@ export const CreateBranchPage = observer(