Skip to content

Commit

Permalink
Lanes: Lane Compare + SnapsDistance GQL API (#7011)
Browse files Browse the repository at this point in the history
  • Loading branch information
luvkapur authored Feb 7, 2023
1 parent 3edfcaa commit 78f1b1b
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 63 deletions.
8 changes: 8 additions & 0 deletions scopes/lanes/lanes/lanes.graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export function lanesSchema(lanesMainRuntime: LanesMain): Schema {
diffOutput: String
}
type SnapDistance {
onSource: [String!]!
onTarget: [String!]!
common: String
}
type FieldsDiff {
fieldName: String!
diffOutput: String
Expand Down Expand Up @@ -60,6 +66,8 @@ export function lanesSchema(lanesMainRuntime: LanesMain): Schema {
"""
changes: [String!]
upToDate: Boolean
snapsDistance: SnapDistance
unrelated: Boolean
}
type LaneDiffStatus {
Expand Down
75 changes: 58 additions & 17 deletions scopes/lanes/lanes/lanes.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import ComponentWriterAspect, { ComponentWriterMain } from '@teambit/component-w
import { SnapsDistance } from '@teambit/legacy/dist/scope/component-ops/snaps-distance';
import { MergingMain, MergingAspect } from '@teambit/merging';
import { ChangeType } from '@teambit/lanes.entities.lane-diff';
import { NoCommonSnap } from '@teambit/legacy/dist/scope/exceptions/no-common-snap';
import { LanesAspect } from './lanes.aspect';
import {
LaneCmd,
Expand All @@ -52,6 +53,12 @@ import { LanesDeleteRoute } from './lanes.delete.route';

export { Lane };

export type SnapsDistanceObj = {
onSource: string[];
onTarget: string[];
common?: string;
};

export type LaneResults = {
lanes: LaneData[];
currentLane?: string | null;
Expand Down Expand Up @@ -82,11 +89,12 @@ export type LaneComponentDiffStatus = {
changeType?: ChangeType;
changes?: ChangeType[];
upToDate?: boolean;
snapsDistance?: SnapsDistanceObj;
unrelated?: boolean;
};

export type LaneDiffStatusOptions = {
skipChanges?: boolean;
skipUpToDate?: boolean;
};

export type LaneDiffStatus = {
Expand Down Expand Up @@ -416,7 +424,12 @@ export class LanesMain {
* @param targetHead head on the target lane. leave empty if the target is main
* @returns
*/
async getSnapsDistance(componentId: ComponentID, sourceHead?: string, targetHead?: string): Promise<SnapsDistance> {
async getSnapsDistance(
componentId: ComponentID,
sourceHead?: string,
targetHead?: string,
throws?: boolean
): Promise<SnapsDistance> {
if (!sourceHead && !targetHead)
throw new Error(`getDivergeData got sourceHead and targetHead empty. at least one of them should be populated`);
const modelComponent = await this.scope.legacyScope.getModelComponent(componentId._legacy);
Expand All @@ -425,6 +438,7 @@ export class LanesMain {
repo: this.scope.legacyScope.objects,
sourceHead: sourceHead ? Ref.from(sourceHead) : modelComponent.head || null,
targetHead: targetHead ? Ref.from(targetHead) : modelComponent.head || null,
throws,
});
}

Expand Down Expand Up @@ -609,21 +623,23 @@ export class LanesMain {
targetLaneId?: LaneId,
options?: LaneDiffStatusOptions
): Promise<LaneDiffStatus> {
const sourceLane = await this.loadLane(sourceLaneId);
if (!sourceLane) throw new Error(`unable to find ${sourceLaneId.toString()} in the scope`);
const sourceLaneComponents = sourceLaneId.isDefault()
? (await this.getLaneDataOfDefaultLane())?.components.map((main) => ({ id: main.id, head: Ref.from(main.head) }))
: (await this.loadLane(sourceLaneId))?.components;

const targetLane = targetLaneId ? await this.loadLane(targetLaneId) : undefined;
const targetLaneIds = targetLane?.toBitIds();
const host = this.componentAspect.getHost();
const diffProps = compact(
await Promise.all(
sourceLane.components.map(async (comp) => {
const componentId = await host.resolveComponentId(comp.id);
const sourceVersionObj = (await this.scope.legacyScope.objects.load(comp.head)) as Version;
if (sourceVersionObj.isRemoved()) {
(sourceLaneComponents || []).map(async ({ id, head }) => {
const componentId = await host.resolveComponentId(id);
const sourceVersionObj = (await this.scope.legacyScope.objects.load(head)) as Version;
if (sourceVersionObj?.isRemoved()) {
return null;
}
const headOnTargetLane = targetLaneIds
? targetLaneIds.searchWithoutVersion(comp.id)?.version
? targetLaneIds.searchWithoutVersion(id)?.version
: await this.getHeadOnMain(componentId);

if (headOnTargetLane) {
Expand All @@ -633,7 +649,7 @@ export class LanesMain {
}
}

const sourceHead = comp.head.toString();
const sourceHead = head.toString();
const targetHead = headOnTargetLane;

return { componentId, sourceHead, targetHead };
Expand All @@ -657,16 +673,29 @@ export class LanesMain {
sourceHead: string,
targetHead?: string,
options?: LaneDiffStatusOptions
) {
const snapsDistance = !options?.skipUpToDate
? await this.getSnapsDistance(componentId, sourceHead, targetHead)
: undefined;
): Promise<LaneComponentDiffStatus> {
const snapsDistance = await this.getSnapsDistance(componentId, sourceHead, targetHead, false);

if (snapsDistance?.err) {
const noCommonSnap = snapsDistance.err instanceof NoCommonSnap;

return {
componentId,
sourceHead,
targetHead,
upToDate: snapsDistance?.isUpToDate(),
unrelated: noCommonSnap || undefined,
changes: [],
};
}

const commonSnap = snapsDistance?.commonSnapBeforeDiverge;

const getChanges = async (): Promise<ChangeType[]> => {
if (!targetHead) return [ChangeType.NEW];
if (!commonSnap) return [ChangeType.NEW];

const compare = await this.componentCompare.compare(
componentId.changeVersion(targetHead).toString(),
componentId.changeVersion(commonSnap.hash).toString(),
componentId.changeVersion(sourceHead).toString()
);

Expand Down Expand Up @@ -695,7 +724,19 @@ export class LanesMain {
const changes = !options?.skipChanges ? await getChanges() : undefined;
const changeType = changes ? changes[0] : undefined;

return { componentId, changeType, changes, sourceHead, targetHead, upToDate: snapsDistance?.isUpToDate() };
return {
componentId,
changeType,
changes,
sourceHead,
targetHead: commonSnap?.hash,
upToDate: snapsDistance?.isUpToDate(),
snapsDistance: {
onSource: snapsDistance?.snapsOnSourceOnly.map((s) => s.hash) ?? [],
onTarget: snapsDistance?.snapsOnTargetOnly.map((s) => s.hash) ?? [],
common: snapsDistance?.commonSnapBeforeDiverge?.hash,
},
};
}

async addLaneReadme(readmeComponentIdStr: string, laneName?: string): Promise<{ result: boolean; message?: string }> {
Expand Down
9 changes: 6 additions & 3 deletions scopes/lanes/lanes/lanes.ui.runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class LanesUI {
}

getLanesComparePage() {
return <LaneComparePage getLaneCompare={this.getLaneCompare} />;
return <LaneComparePage getLaneCompare={this.getLaneCompare} groupByScope={this.lanesHost === 'workspace'} />;
}

getMenuRoutes() {
Expand Down Expand Up @@ -209,10 +209,13 @@ export class LanesUI {
{
props: {
href: '~compare',
children: 'Lane Compare',
children: 'Compare',
},
order: 2,
hide: () => true,
hide: () => {
const { lanesModel } = useLanes();
return !lanesModel?.viewedLane || lanesModel?.lanes.length < 2;
},
},
]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const QUERY_LANE_DIFF_STATUS = gql`
targetHead
changes
upToDate
unrelated
}
}
}
Expand Down Expand Up @@ -80,6 +81,7 @@ export const useLaneDiffStatus: UseLaneDiffStatus = ({ baseId, compareId, option
targetLane: LaneId.from(data.lanes.diffStatus.target.name, data.lanes.diffStatus.target.scope).toString(),
diff: data.lanes.diffStatus.componentsStatus.map((c) => ({
...c,
changes: c.changes || [],
componentId: ComponentID.fromObject(c.componentId).toString(),
})),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,49 @@
flex-direction: column;
height: 100%;
width: 100%;
// overflow: scroll;
}
.top {
display: flex;
padding: 32px;
padding: 24px;
padding-bottom: 8px;
align-items: center;
width: 60%;

> div {
display: flex;
}
}

.bottom {
display: flex;
overflow: scroll;
}

.compareLane {
padding: 8px;
background: var(--surface-neutral-focus-color);
border-radius: 6px;
margin: 0px 4px;
width: fit-content;
// same height as the lane selector
height: 34px;
box-sizing: border-box;
font-size: var(--bit-p-xs, 14px);
align-items: center;
}

.baseSelectorContainer {
padding: 0px 4px;
width: 100%;
min-width: 100px;
max-width: 200px;
> div:first-of-type {
width: 100%;
}
}

.laneIcon {
padding-right: 4px;
height: 16px;
font-size: var(--bit-p-sm, 16px);
}
59 changes: 52 additions & 7 deletions scopes/lanes/ui/compare/lane-compare-page/lane-compare-page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,71 @@
import React, { HTMLAttributes } from 'react';
import React, { HTMLAttributes, useState, useEffect, useMemo } from 'react';
import { LaneCompareProps } from '@teambit/lanes';
import { useLanes } from '@teambit/lanes.hooks.use-lanes';
import { LaneSelector } from '@teambit/lanes.ui.inputs.lane-selector';
import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
import { LaneId } from '@teambit/lane-id';
import { LaneIcon } from '@teambit/lanes.ui.icons.lane-icon';

import styles from './lane-compare-page.module.scss';

export type LaneComparePageProps = {
getLaneCompare: (props: LaneCompareProps) => React.ReactNode;
groupByScope?: boolean;
} & HTMLAttributes<HTMLDivElement>;

export function LaneComparePage({ getLaneCompare, ...rest }: LaneComparePageProps) {
export function LaneComparePage({ getLaneCompare, groupByScope, ...rest }: LaneComparePageProps) {
const { lanesModel } = useLanes();
const [base, setBase] = useState<LaneModel | undefined>();
const defaultLane = lanesModel?.getDefaultLane();
const compare = lanesModel?.viewedLane;
const nonMainLanes = lanesModel?.getNonMainLanes();
useEffect(() => {
if (!base && !compare?.id.isDefault() && defaultLane) {
setBase(defaultLane);
}
if (!base && compare?.id.isDefault() && (nonMainLanes?.length ?? 0) > 0) {
setBase(nonMainLanes?.[0]);
}
}, [defaultLane, compare?.id.toString(), nonMainLanes?.length]);

if (!lanesModel) return null;
const LaneCompareComponent = useMemo(() => {
return getLaneCompare({ base, compare });
}, [base?.id.toString(), compare?.id.toString()]);

const compare = lanesModel.getDefaultLane();
const base = lanesModel.getNonMainLanes()[0];
const lanes: Array<LaneId> = useMemo(() => {
const mainLaneId = defaultLane?.id;
const nonMainLaneIds = nonMainLanes?.map((lane) => lane.id) || [];
const allLanes = (mainLaneId && [mainLaneId, ...nonMainLaneIds]) || nonMainLaneIds;
return allLanes.filter((l) => l.toString() !== compare?.id.toString());
}, [base?.id.toString(), compare?.id.toString(), lanesModel?.lanes.length]);

const LaneCompareComponent = getLaneCompare({ base, compare });
if (!lanesModel) return null;
if (!lanesModel.viewedLane) return null;
if (!base) return null;

return (
<div {...rest} className={styles.laneComparePage}>
{LaneCompareComponent}
<div className={styles.top}>
<div>Compare</div>
<div className={styles.compareLane}>
<LaneIcon className={styles.laneIcon} />
{compare?.id.name}
</div>
<div>with</div>
<div className={styles.baseSelectorContainer}>
<LaneSelector
selectedLaneId={base.id}
className={styles.baseSelector}
lanes={lanes}
groupByScope={groupByScope}
getHref={() => ''}
onLaneSelected={(laneId) => {
setBase(lanesModel?.lanes.find((l) => l.id.toString() === laneId.toString()));
}}
/>
</div>
</div>
<div className={styles.bottom}>{LaneCompareComponent}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@import '~@teambit/ui-foundation.ui.constants.z-indexes/z-indexes.module.scss';

.rootLaneCompare {
position: relative;
height: 100%;
Expand Down Expand Up @@ -42,7 +40,7 @@
top: 0;
left: 0;
background-color: var(--background-color);
z-index: $modal-z-index;
z-index: 1;
padding: 0;
}

Expand Down
Loading

0 comments on commit 78f1b1b

Please sign in to comment.