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

Branch graph update #388

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
72 changes: 52 additions & 20 deletions shared/common/branchGraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,52 @@ export const BranchGraph = observer(function BranchGraph({
instanceState,
...props
}: BranchGraphProps) {
const fetching = useRef(false);
const [refreshing, setRefreshing] = useState(true);
const [layoutNodes, setLayoutNodes] = useState<LayoutNode[] | null>(null);

const manualRefresh =
instanceState instanceof InstanceState &&
!!instanceState.databases?.length &&
instanceState.databases[0].last_migration === undefined;

useEffect(() => {
if (
fetching.current ||
!refreshing ||
!instanceId ||
!(instanceState instanceof InstanceState)
) {
return;
}
fetchMigrationsData(instanceId, instanceState).then((data) => {
if (!data) return;

const layoutNodes = joinGraphLayouts(buildBranchGraph(data));
setLayoutNodes(layoutNodes);
setRefreshing(false);
});
fetching.current = true;
instanceState.fetchDatabaseInfo().then(() =>
fetchMigrationsData(instanceId, instanceState).then((data) => {
if (!data) return;

const layoutNodes = joinGraphLayouts(buildBranchGraph(data));
setLayoutNodes(layoutNodes);
setRefreshing(false);
fetching.current = false;
})
);
}, [refreshing, instanceId, instanceState]);

useEffect(() => {
if (!manualRefresh) {
const listener = () => {
if (document.visibilityState === "visible") {
setRefreshing(true);
}
};
document.addEventListener("visibilitychange", listener);

return () => {
document.removeEventListener("visibilitychange", listener);
};
}
}, [manualRefresh]);

const fetchMigrations =
instanceState instanceof Error
? () => {
Expand Down Expand Up @@ -157,19 +183,25 @@ export const BranchGraph = observer(function BranchGraph({
instanceState instanceof Error ? instanceState : layoutNodes
}
{...props}
TopButton={({className}) => (
<button
className={cn(className, {
[styles.refreshing]: refreshing,
})}
onClick={() => {
localStorage.removeItem(`edgedb-branch-graph-${instanceId}`);
setRefreshing(true);
}}
>
<SyncIcon />
</button>
)}
TopButton={
manualRefresh
? ({className}) => (
<button
className={cn(className, {
[styles.refreshing]: refreshing,
})}
onClick={() => {
localStorage.removeItem(
`edgedb-branch-graph-${instanceId}`
);
setRefreshing(true);
}}
>
<SyncIcon />
</button>
)
: undefined
}
/>
</BranchGraphContext.Provider>
);
Expand Down
59 changes: 40 additions & 19 deletions shared/common/branchGraph/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,35 +72,56 @@ export async function fetchMigrationsData(
return null;
}

const databases = new Set(instanceState.databases);
const databases = new Map(
instanceState.databases.map((db) => [db.name, db.last_migration])
);

let migrationsData = _getBranchGraphDataFromCache(instanceId);

if (
!migrationsData ||
migrationsData.length != databases.size ||
!migrationsData.every(({branch}) => databases.has(branch))
migrationsData &&
migrationsData.length == databases.size &&
migrationsData.every(
({branch, migrations}) =>
databases.get(branch) ===
(migrations[migrations.length - 1]?.name ?? null)
)
) {
migrationsData = await Promise.all(
instanceState.databases.map(async (dbName) => {
const conn = instanceState.getConnection(dbName);
return {
branch: dbName,
migrations: sortMigrations(
((
await conn.query(`
// all cached data is still valid
return migrationsData;
}

migrationsData = await Promise.all(
instanceState.databases.map(async ({name, last_migration}) => {
if (last_migration !== undefined) {
const cachedMigration = migrationsData?.find((d) => d.branch === name);
if (
cachedMigration &&
(cachedMigration.migrations[cachedMigration.migrations.length - 1]
?.name ?? null) === last_migration
) {
// return cached data for branch if still valid
return cachedMigration;
}
}

const conn = instanceState.getConnection(name);
return {
branch: name,
migrations: sortMigrations(
((
await conn.query(`
select schema::Migration {
id,
name,
parentId := assert_single(.parents.id)
}`)
).result as Migration[]) ?? []
),
};
})
);
_storeBranchGraphDataInCache(instanceId, migrationsData);
}
).result as Migration[]) ?? []
),
};
})
);
_storeBranchGraphDataInCache(instanceId, migrationsData);

return migrationsData;
}
Expand Down
4 changes: 3 additions & 1 deletion shared/common/components/dataGrid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,19 +174,21 @@ export const GridContent = observer(function GridContent({
state,
pinnedCells,
cells,
bottomPadding,
}: {
className?: string;
style?: React.CSSProperties;
state: DataGridState;
pinnedCells?: React.ReactNode;
cells: React.ReactNode;
bottomPadding?: number;
}) {
return (
<div
className={cn(styles.gridContent, className)}
style={{
...style,
height: state.gridContentHeight,
height: state.gridContentHeight + (bottomPadding ?? 0),
width: state.gridContentWidth,
}}
>
Expand Down
2 changes: 1 addition & 1 deletion shared/common/components/infoCards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function InfoCards({
className?: string;
listClassName?: string;
extraCards?: (InfoCardDef | null)[];
currentVersion?: {major: number; minor: number; stage: string} | null;
currentVersion?: {major: number; minor: number} | null;
}) {
const data = useLatestInfo();

Expand Down
6 changes: 5 additions & 1 deletion shared/common/components/resultGrid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export {createResultGridState, ResultGridState} from "./state";

export interface ResultGridProps extends Omit<DataGridProps, "state"> {
state: ResultGridState;
bottomPadding?: number;
}

export const ResultGrid = observer(function ResultGrid({
state,
bottomPadding,
...props
}: ResultGridProps) {
useEffect(() => {
Expand All @@ -51,7 +53,7 @@ export const ResultGrid = observer(function ResultGrid({
return (
<DataGrid state={state.grid} {...props}>
<ResultGridHeaders state={state} />
<ResultGridContent state={state} />
<ResultGridContent state={state} bottomPadding={bottomPadding} />
</DataGrid>
);
});
Expand Down Expand Up @@ -131,6 +133,7 @@ export const ResultGridHeaders = observer(function ResultGridHeaders({

export const ResultGridContent = observer(function ResultGridContent({
state,
bottomPadding,
}: ResultGridProps) {
const ranges = state.grid.visibleRanges;
const rowTops = state.rowTops;
Expand Down Expand Up @@ -191,6 +194,7 @@ export const ResultGridContent = observer(function ResultGridContent({
style={{"--gridHeaderHeight": state.grid.headerHeight + "px"} as any}
state={state.grid}
cells={cells}
bottomPadding={bottomPadding}
/>
);
});
Expand Down
1 change: 1 addition & 0 deletions shared/common/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

$breakpoints: (
mobile: 768px,
tablet: 1024px,
);

@mixin breakpoint($breakpoint) {
Expand Down
4 changes: 2 additions & 2 deletions shared/common/newui/copyButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Button} from "../button";

export interface CopyButtonProps {
className?: string;
content: string;
content: string | (() => string);
mini?: boolean;
}

Expand All @@ -25,7 +25,7 @@ export function CopyButton({className, content, mini}: CopyButtonProps) {
}, [copied]);

const onCopy = () => {
navigator.clipboard?.writeText(content);
navigator.clipboard?.writeText(typeof content === 'string' ? content : content());
setCopied(true);
};

Expand Down
12 changes: 10 additions & 2 deletions shared/common/newui/fieldHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import cn from "@edgedb/common/utils/classNames";

import styles from "./fieldHeader.module.scss";

export interface FieldHeaderProps {
className?: string;
label?: string | JSX.Element;
optional?: boolean;
headerNote?: string;
}

export function FieldHeader({label, optional, headerNote}: FieldHeaderProps) {
export function FieldHeader({
className,
label,
optional,
headerNote,
}: FieldHeaderProps) {
return (
<div className={styles.fieldHeader}>
<div className={cn(styles.fieldHeader, className)}>
{label}
{optional ? <span className={styles.optional}>(optional)</span> : null}
{headerNote ? (
Expand Down
93 changes: 93 additions & 0 deletions shared/common/newui/iconToggle/iconToggle.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
@import "@edgedb/common/mixins.scss";

.iconToggle {
display: flex;
background: var(--Grey95);
border: 1px solid var(--Grey90);
border-radius: 8px;
font-family: "Roboto Flex Variable", sans-serif;
line-height: 16px;

&.disabled {
opacity: 0.4;
pointer-events: none;
}

@include darkTheme {
background: var(--Grey16);
border-color: var(--Grey14);
}

@include isMobile {
border-radius: 10px;
}
}

.option {
position: relative;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;

svg {
color: var(--Grey55);
z-index: 1;

@include darkTheme {
color: var(--Grey65);
}
}

&.selected:before {
content: "";
position: absolute;
inset: 2px;
border-radius: 6px;
background: #fff;
box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.16);

@include darkTheme {
background: var(--Grey30);
box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.4);
}
}

&.disabled {
opacity: 0.3;
pointer-events: none;
}

.label {
position: absolute;
top: calc(100% + 2px);
white-space: nowrap;
background: var(--panel_background);
padding: 6px 10px;
border-radius: 6px;
color: var(--secondary_text_color);
font-weight: 500;
font-size: 13px;
border: 1px solid var(--panel_border);
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.08);
opacity: 0;
pointer-events: none;
transition: opacity 0.15s linear;
}

&:hover .label {
opacity: 1;
transition-delay: 0.4s;
}

@include isMobile {
width: 38px;
height: 38px;

&.selected:before {
border-radius: 8px;
}
}
}
Loading
Loading