Skip to content

Commit

Permalink
Patch APIS
Browse files Browse the repository at this point in the history
  • Loading branch information
Caleb-T-Owens committed Jan 9, 2025
1 parent e3a20f4 commit 1f37d10
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 7 deletions.
2 changes: 2 additions & 0 deletions apps/desktop/src/lib/redux/store.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { branchesReducer } from '@gitbutler/shared/branches/branchesSlice';
import { patchSectionsReducer } from '@gitbutler/shared/branches/patchSectionsSlice';
import { patchesReducer } from '@gitbutler/shared/branches/patchesSlice';
import { feedsReducer } from '@gitbutler/shared/feeds/feedsSlice';
import { postsReducer } from '@gitbutler/shared/feeds/postsSlice';
Expand Down Expand Up @@ -59,6 +60,7 @@ export class DesktopState extends AppState implements AppDesktopOnlyState {
projects: projectsReducer,
branches: branchesReducer,
patches: patchesReducer,
patchSections: patchSectionsReducer,
desktopOnly: desktopOnly.reducer
}
});
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import * as events from '$lib/utils/events';
import { unsubscribe } from '$lib/utils/unsubscribe';
import { BranchService as CloudBranchService } from '@gitbutler/shared/branches/branchService';
import { PatchService as CloudPatchService } from '@gitbutler/shared/branches/patchService';
import { FeedService } from '@gitbutler/shared/feeds/service';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { OrganizationService } from '@gitbutler/shared/organizations/organizationService';
Expand Down Expand Up @@ -72,6 +73,7 @@
const cloudUserService = new CloudUserService(data.cloud, appState.appDispatch);
const cloudProjectService = new CloudProjectService(data.cloud, appState.appDispatch);
const cloudBranchService = new CloudBranchService(data.cloud, appState.appDispatch);
const cloudPatchService = new CloudPatchService(data.cloud, appState.appDispatch);
setContext(AppState, appState);
setContext(AppDispatch, appState.appDispatch);
Expand All @@ -82,6 +84,7 @@
setContext(CloudUserService, cloudUserService);
setContext(CloudProjectService, cloudProjectService);
setContext(CloudBranchService, cloudBranchService);
setContext(CloudPatchService, cloudPatchService);
// Setters do not need to be reactive since `data` never updates
setSecretsService(data.secretsService);
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import Navigation from '$lib/components/Navigation.svelte';
import { UserService } from '$lib/user/userService';
import { BranchService } from '@gitbutler/shared/branches/branchService';
import { PatchService } from '@gitbutler/shared/branches/patchService';
import { FeedService } from '@gitbutler/shared/feeds/service';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { OrganizationService } from '@gitbutler/shared/organizations/organizationService';
Expand Down Expand Up @@ -46,9 +47,10 @@
setContext(ProjectService, projectService);
const newUserService = new NewUserService(httpClient, appState.appDispatch);
setContext(NewUserService, newUserService);
const branchService = new BranchService(httpClient, appState.appDispatch);
setContext(BranchService, branchService);
const patchSerice = new PatchService(httpClient, appState.appDispatch);
setContext(PatchService, patchSerice);
$effect(() => {
const token = get(authService.token) || $page.url.searchParams.get('gb_access_token');
Expand Down
39 changes: 37 additions & 2 deletions packages/shared/src/lib/branches/branchService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { upsertBranch, upsertBranches } from '$lib/branches/branchesSlice';
import { addBranch, upsertBranch, upsertBranches } from '$lib/branches/branchesSlice';
import { upsertPatches } from '$lib/branches/patchesSlice';
import {
apiToBranch,
Expand All @@ -10,6 +10,7 @@ import {
type LoadablePatch
} from '$lib/branches/types';
import { InterestStore, type Interest } from '$lib/interest/intrestStore';
import { errorToLoadable } from '$lib/network/loadable';
import { POLLING_GLACIALLY, POLLING_REGULAR } from '$lib/polling';
import type { HttpClient } from '$lib/network/httpClient';
import type { AppDispatch } from '$lib/redux/store.svelte';
Expand All @@ -32,7 +33,10 @@ export class BranchService {
private readonly appDispatch: AppDispatch
) {}

getBranches(repositoryId: string, branchStatus: BranchStatus = BranchStatus.All): Interest {
getBranchesInterest(
repositoryId: string,
branchStatus: BranchStatus = BranchStatus.All
): Interest {
return this.branchesInterests
.findOrCreateSubscribable({ repositoryId, branchStatus }, async () => {
try {
Expand Down Expand Up @@ -66,6 +70,37 @@ export class BranchService {
.createInterest();
}

getBranchInterest(repositoryId: string, branchId: string): Interest {
return this.branchInterests
.findOrCreateSubscribable({ branchId }, async () => {
this.appDispatch.dispatch(addBranch({ status: 'loading', id: branchId }));
try {
const apiBranch = await this.httpClient.get<ApiBranch>(
`patch_stack/${repositoryId}/${branchId}`
);
const branch: LoadableBranch = {
status: 'found',
id: apiBranch.branch_id,
value: apiToBranch(apiBranch)
};

const patches = apiBranch.patches.map(
(api): LoadablePatch => ({
status: 'found',
id: api.change_id,
value: apiToPatch(api)
})
);

this.appDispatch.dispatch(upsertBranch(branch));
this.appDispatch.dispatch(upsertPatches(patches));
} catch (error: unknown) {
this.appDispatch.dispatch(upsertBranch(errorToLoadable(error, branchId)));
}
})
.createInterest();
}

async createBranch(repositoryId: string, branchId: string, oplogSha: string): Promise<Branch> {
const apiBranch = await this.httpClient.post<ApiBranch>(`patch_stack`, {
body: { branch_id: branchId, oplog_sha: oplogSha, project_id: repositoryId }
Expand Down
44 changes: 44 additions & 0 deletions packages/shared/src/lib/branches/branchesPreview.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { branchesSelectors } from '$lib/branches/branchesSlice';
import { BranchStatus, type LoadableBranch } from '$lib/branches/types';
import { registerInterest, type InView } from '$lib/interest/registerInterestFunction.svelte';
import type { BranchService } from '$lib/branches/branchService';
import type { AppBranchesState } from '$lib/redux/store.svelte';
import type { Reactive } from '$lib/storeUtils';

export function getBranchReviews(
appState: AppBranchesState,
branchService: BranchService,
repositoryId: string,
status: BranchStatus = BranchStatus.All,
inView?: InView
): Reactive<LoadableBranch[]> {
const branchReviewsInterest = branchService.getBranchesInterest(repositoryId, status);
registerInterest(branchReviewsInterest, inView);

const branchReviews = $derived(branchesSelectors.selectAll(appState.branches));

return {
get current() {
return branchReviews;
}
};
}

export function getBranchReview(
appState: AppBranchesState,
branchService: BranchService,
repositoryId: string,
branchId: string,
inView?: InView
): Reactive<LoadableBranch | undefined> {
const branchReviewInterest = branchService.getBranchInterest(repositoryId, branchId);
registerInterest(branchReviewInterest, inView);

const branchReview = $derived(branchesSelectors.selectById(appState.branches, branchId));

return {
get current() {
return branchReview;
}
};
}
31 changes: 31 additions & 0 deletions packages/shared/src/lib/branches/patchSectionsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import type { Section } from '$lib/branches/types';

const patchSectionsAdapter = createEntityAdapter<Section, Section['id']>({
selectId: (patchSection: Section) => patchSection.id
});

const patchSectionsSlice = createSlice({
name: 'patchSectionSections',
initialState: patchSectionsAdapter.getInitialState(),
reducers: {
addPatchSection: patchSectionsAdapter.addOne,
addPatchSections: patchSectionsAdapter.addMany,
removePatchSection: patchSectionsAdapter.removeOne,
removePatchSections: patchSectionsAdapter.removeMany,
upsertPatchSection: patchSectionsAdapter.upsertOne,
upsertPatchSections: patchSectionsAdapter.upsertMany
}
});

export const patchSectionsReducer = patchSectionsSlice.reducer;

export const patchSectionsSelectors = patchSectionsAdapter.getSelectors();
export const {
addPatchSection,
addPatchSections,
removePatchSection,
removePatchSections,
upsertPatchSection,
upsertPatchSections
} = patchSectionsSlice.actions;
72 changes: 72 additions & 0 deletions packages/shared/src/lib/branches/patchService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { upsertPatchSections } from '$lib/branches/patchSectionsSlice';
import { addPatch, upsertPatch } from '$lib/branches/patchesSlice';
import { apiToPatch, apiToSection, type ApiPatch, type Patch } from '$lib/branches/types';
import { InterestStore, type Interest } from '$lib/interest/intrestStore';
import { errorToLoadable } from '$lib/network/loadable';
import { POLLING_REGULAR } from '$lib/polling';
import type { HttpClient } from '$lib/network/httpClient';
import type { AppDispatch } from '$lib/redux/store.svelte';

type PatchUpdateParams = {
signOff?: boolean;
sectionOrder?: string[];
};

export class PatchService {
private readonly patchInterests = new InterestStore<{ changeId: string }>(POLLING_REGULAR);

constructor(
private readonly httpClient: HttpClient,
private readonly appDispatch: AppDispatch
) {}

getPatchWithSectionsInterest(branchId: string, changeId: string): Interest {
return this.patchInterests
.findOrCreateSubscribable({ changeId }, async () => {
this.appDispatch.dispatch(addPatch({ status: 'loading', id: changeId }));
try {
const apiPatch = await this.httpClient.get<ApiPatch>(
`patch_stack/${branchId}/patch/${changeId}`
);

const patch = apiToPatch(apiPatch);

// This will always be here, but this makes the typescript
// compiler happy
if (apiPatch.sections) {
const sections = apiPatch.sections.map(apiToSection);
this.appDispatch.dispatch(upsertPatchSections(sections));
}

this.appDispatch.dispatch(upsertPatch({ status: 'found', id: changeId, value: patch }));
} catch (error: unknown) {
this.appDispatch.dispatch(upsertPatch(errorToLoadable(error, changeId)));
}
})
.createInterest();
}

async updatePatch(branchId: string, changeId: string, params: PatchUpdateParams): Promise<Patch> {
const apiPatch = await this.httpClient.patch<ApiPatch>(
`patch_stack/${branchId}/patch/${changeId}`,
{
body: {
sign_off: params.signOff,
section_order: params.sectionOrder
}
}
);

const patch = apiToPatch(apiPatch);
this.appDispatch.dispatch(upsertPatch({ status: 'found', id: changeId, value: patch }));

// This will always be here, but this makes the typescript
// compiler happy
if (apiPatch.sections) {
const sections = apiPatch.sections.map(apiToSection);
this.appDispatch.dispatch(upsertPatchSections(sections));
}

return patch;
}
}
52 changes: 52 additions & 0 deletions packages/shared/src/lib/branches/patchesPreview.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { patchSectionsSelectors } from '$lib/branches/patchSectionsSlice';
import { patchesSelectors } from '$lib/branches/patchesSlice';
import { registerInterest, type InView } from '$lib/interest/registerInterestFunction.svelte';
import type { PatchService } from '$lib/branches/patchService';
import type { LoadablePatch, Section } from '$lib/branches/types';
import type { AppPatchesState, AppPatchSectionsState } from '$lib/redux/store.svelte';
import type { Reactive } from '$lib/storeUtils';

export function getPatch(
appState: AppPatchesState,
patchService: PatchService,
branchId: string,
changeId: string,
inView?: InView
): Reactive<LoadablePatch | undefined> {
const patchInterest = patchService.getPatchWithSectionsInterest(branchId, changeId);
registerInterest(patchInterest, inView);

const patch = $derived(patchesSelectors.selectById(appState.patches, branchId));

return {
get current() {
return patch;
}
};
}

export function getPatchSections(
appState: AppPatchesState & AppPatchSectionsState,
patchService: PatchService,
branchId: string,
changeId: string,
inView?: InView
): Reactive<Section[] | undefined> {
const patchInterest = patchService.getPatchWithSectionsInterest(branchId, changeId);
registerInterest(patchInterest, inView);

const patch = $derived(patchesSelectors.selectById(appState.patches, branchId));
const sections = $derived.by(() => {
if (patch?.status !== 'found') return;

return (patch.value.sectionIds || [])
.map((id) => patchSectionsSelectors.selectById(appState.patchSections, id))
.filter((a) => a) as Section[];
});

return {
get current() {
return sections;
}
};
}
Loading

0 comments on commit 1f37d10

Please sign in to comment.