Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add-new-stuff #5911

Open
wants to merge 4 commits into
base: remove-old-stuff
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/desktop/src/lib/redux/store.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
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';
import { organizationsReducer } from '@gitbutler/shared/organizations/organizationsSlice';
Expand Down Expand Up @@ -55,6 +58,9 @@ export class DesktopState extends AppState implements AppDesktopOnlyState {
orgnaizations: organizationsReducer,
users: usersReducer,
projects: projectsReducer,
branches: branchesReducer,
patches: patchesReducer,
patchSections: patchSectionsReducer,
desktopOnly: desktopOnly.reducer
}
});
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import { User, UserService } from '$lib/stores/user';
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 @@ -70,6 +72,8 @@
const organizationService = new OrganizationService(data.cloud, appState.appDispatch);
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 @@ -79,6 +83,8 @@
setContext(OrganizationService, organizationService);
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
6 changes: 6 additions & 0 deletions apps/web/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { AuthService } from '$lib/auth/authService';
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 @@ -45,6 +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
147 changes: 147 additions & 0 deletions packages/shared/src/lib/branches/branchService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { addBranch, upsertBranch, upsertBranches } from '$lib/branches/branchesSlice';
import { upsertPatches } from '$lib/branches/patchesSlice';
import {
apiToBranch,
apiToPatch,
BranchStatus,
type ApiBranch,
type Branch,
type LoadableBranch,
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';

type BranchUpdateParams = {
status: BranchStatus.Active | BranchStatus.Closed;
title: string;
description: string;
};

export class BranchService {
private readonly branchesInterests = new InterestStore<{
repositoryId: string;
branchStatus: BranchStatus;
}>(POLLING_GLACIALLY);
private readonly branchInterests = new InterestStore<{ branchId: string }>(POLLING_REGULAR);

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

getBranchesInterest(
repositoryId: string,
branchStatus: BranchStatus = BranchStatus.All
): Interest {
return this.branchesInterests
.findOrCreateSubscribable({ repositoryId, branchStatus }, async () => {
try {
const apiBranches = await this.httpClient.get<ApiBranch[]>(
`patch_stack/${repositoryId}?status=${branchStatus}`
);
const branches = apiBranches.map(
(api): LoadableBranch => ({
status: 'found',
id: api.branch_id,
value: apiToBranch(api)
})
);

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

this.appDispatch.dispatch(upsertBranches(branches));
this.appDispatch.dispatch(upsertPatches(patches));
} catch (_error: unknown) {
/* empty */
}
})
.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 }
});
const branch = apiToBranch(apiBranch);

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

this.appDispatch.dispatch(
upsertBranch({
status: 'found',
id: branch.branchId,
value: branch
})
);
this.appDispatch.dispatch(upsertPatches(patches));

return branch;
}

async updateBranch(branchId: string, params: BranchUpdateParams): Promise<Branch> {
const apiBranch = await this.httpClient.put<ApiBranch>(`patch_stack/${branchId}`, {
body: params
});
const branch = apiToBranch(apiBranch);

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

this.appDispatch.dispatch(
upsertBranch({
status: 'found',
id: branch.branchId,
value: branch
})
);
this.appDispatch.dispatch(upsertPatches(patches));

return branch;
}
}
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;
}
};
}
32 changes: 32 additions & 0 deletions packages/shared/src/lib/branches/branchesSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { loadableUpsert, loadableUpsertMany } from '$lib/network/loadable';
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import type { LoadableBranch } from '$lib/branches/types';

const branchesAdapter = createEntityAdapter<LoadableBranch, LoadableBranch['id']>({
selectId: (branch: LoadableBranch) => branch.id
});

const branchesSlice = createSlice({
name: 'branches',
initialState: branchesAdapter.getInitialState(),
reducers: {
addBranch: branchesAdapter.addOne,
addBranches: branchesAdapter.addMany,
removeBranch: branchesAdapter.removeOne,
removeBranches: branchesAdapter.removeMany,
upsertBranch: loadableUpsert(branchesAdapter),
upsertBranches: loadableUpsertMany(branchesAdapter)
}
});

export const branchesReducer = branchesSlice.reducer;

export const branchesSelectors = branchesAdapter.getSelectors();
export const {
addBranch,
addBranches,
removeBranch,
removeBranches,
upsertBranch,
upsertBranches
} = branchesSlice.actions;
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;
}
}
Loading
Loading