Skip to content

Commit

Permalink
Refresh change request cache when click refresh button (#2489)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamyPesse authored Sep 25, 2024
1 parent 16e6171 commit e09f747
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-peas-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gitbook': minor
---

Revalidate change request cached content when pressing refresh button
72 changes: 8 additions & 64 deletions packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import React from 'react';
import { ContentPointer, getChangeRequest, getRevision } from '@/lib/api';
import { tcls } from '@/lib/tailwind';

import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
import { Toolbar, ToolbarBody, ToolbarButton, ToolbarButtonGroups } from './Toolbar';
import { DateRelative } from '../primitives';

interface AdminToolbarProps {
Expand Down Expand Up @@ -86,9 +88,12 @@ async function ChangeRequestToolbar(props: { spaceId: string; changeRequestId: s
<ToolbarButton title="Open in application" href={changeRequest.urls.app}>
<Icon icon="arrow-up-right-from-square" className="size-4" />
</ToolbarButton>
<ToolbarButton title="Refresh" href={'?'}>
<Icon icon="rotate-right" className="size-4" />
</ToolbarButton>
<RefreshChangeRequestButton
spaceId={spaceId}
changeRequestId={changeRequestId}
revisionId={changeRequest.revision}
updatedAt={new Date(changeRequest.updatedAt).getTime()}
/>
</ToolbarButtonGroups>
</Toolbar>
);
Expand Down Expand Up @@ -130,64 +135,3 @@ async function RevisionToolbar(props: { spaceId: string; revisionId: string }) {
</Toolbar>
);
}

function Toolbar(props: { children: React.ReactNode }) {
const { children } = props;

return (
<div
className={tcls(
'flex',
'flex-row',
'items-center',
'gap-4',
'text-sm',
'px-4',
'py-1',
'rounded-full',
'truncate',
'text-light',
'dark:text-light',
)}
>
{children}
</div>
);
}

function ToolbarBody(props: { children: React.ReactNode }) {
return <div className="flex flex-col gap-1">{props.children}</div>;
}

function ToolbarButtonGroups(props: { children: React.ReactNode }) {
return <div className="flex flex-row gap-2">{props.children}</div>;
}

function ToolbarButton(props: { title: string; href: string; children: React.ReactNode }) {
const { title, href, children } = props;
return (
<a
title={title}
href={href}
className={tcls(
'flex',
'flex-col',
'items-center',
'justify-center',
'size-11',
'gap-1',
'text-sm',
'rounded-full',
'hover:bg-dark-1',
'hover:text-white',
'truncate',
'text-light',
'dark:text-light',
'dark:hover:bg-dark-2',
'hover:shadow-lg',
)}
>
{children}
</a>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';
import { Icon } from '@gitbook/icons';
import React from 'react';

import { useCheckForContentUpdate } from '@/components/AutoRefreshContent';
import { tcls } from '@/lib/tailwind';

import { ToolbarButton } from './Toolbar';

// We don't show the button if the content has been updated 30s ago or less.
const minInterval = 1000 * 30; // 5 minutes

/**
* Button to refresh the page if the content has been updated.
*/
export function RefreshChangeRequestButton(props: {
spaceId: string;
changeRequestId: string;
revisionId: string;
updatedAt: number;
}) {
const { updatedAt } = props;

const [visible, setVisible] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const checkForUpdates = useCheckForContentUpdate(props);

const refresh = React.useCallback(async () => {
setLoading(true);
try {
await checkForUpdates();
} finally {
setLoading(false);
setVisible(false);
}
}, [checkForUpdates]);

// Show the button if the content has been updated more than 30s ago.
React.useEffect(() => {
if (updatedAt < Date.now() - minInterval) {
setVisible(true);
}
}, [updatedAt]);

// 30sec after being hidden, we show the button again
React.useEffect(() => {
if (!visible) {
const timeout = setTimeout(() => {
setVisible(true);
}, minInterval);
return () => clearTimeout(timeout);
}
}, [visible]);

if (!visible) {
return null;
}

return (
<ToolbarButton
title="Refresh"
onClick={(event) => {
event.preventDefault();
refresh();
}}
>
<Icon icon="rotate" className={tcls('size-4', loading ? 'animate-spin' : null)} />
</ToolbarButton>
);
}
66 changes: 66 additions & 0 deletions packages/gitbook/src/components/AdminToolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client';

import * as React from 'react';

import { tcls } from '@/lib/tailwind';

export function Toolbar(props: { children: React.ReactNode }) {
const { children } = props;

return (
<div
className={tcls(
'flex',
'flex-row',
'items-center',
'gap-4',
'text-sm',
'px-4',
'py-1',
'rounded-full',
'truncate',
'text-light',
'dark:text-light',
)}
>
{children}
</div>
);
}

export function ToolbarBody(props: { children: React.ReactNode }) {
return <div className="flex flex-col gap-1">{props.children}</div>;
}

export function ToolbarButtonGroups(props: { children: React.ReactNode }) {
return <div className="flex flex-row gap-2">{props.children}</div>;
}

export function ToolbarButton(props: React.HTMLProps<HTMLAnchorElement>) {
const { children, ...rest } = props;
return (
<a
{...rest}
className={tcls(
'flex',
'flex-col',
'items-center',
'justify-center',
'size-11',
'gap-1',
'text-sm',
'rounded-full',
'hover:bg-dark-1',
'hover:text-white',
'truncate',
'text-light',
'dark:text-light',
'dark:hover:bg-dark-2',
'hover:shadow-lg',
'cursor-pointer',
)}
>
{children}
</a>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useCheckForContentUpdate';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use server';

import { getChangeRequest } from '@/lib/api';

/**
* Return true if a change-request has been updated.
*/
export async function hasContentBeenUpdated(props: {
spaceId: string;
changeRequestId: string;
revisionId: string;
}) {
const changeRequest = await getChangeRequest.revalidate(props.spaceId, props.changeRequestId);
return changeRequest.revision !== props.revisionId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import React from 'react';

import { hasContentBeenUpdated } from './server-actions';

/**
* Return a callback to check if a change request has been updated and to refresh the page if it has.
*/
export function useCheckForContentUpdate(props: {
spaceId: string;
changeRequestId: string;
revisionId: string;
}) {
const { spaceId, changeRequestId, revisionId } = props;

return React.useCallback(async () => {
const updated = await hasContentBeenUpdated({ spaceId, changeRequestId, revisionId });

if (updated) {
window.location.reload();
}
}, [spaceId, changeRequestId, revisionId]);
}
5 changes: 3 additions & 2 deletions packages/gitbook/src/lib/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type CacheFunction<Args extends any[], Result> = ((
/**
* Refetch the data and update the cache.
*/
revalidate: (...args: Args | [...Args, CacheFunctionOptions]) => Promise<void>;
revalidate: (...args: Args | [...Args, CacheFunctionOptions]) => Promise<Result>;

/**
* Check if a value is in the memory cache.
Expand Down Expand Up @@ -239,7 +239,8 @@ export function cache<Args extends any[], Result>(
const cacheArgs = cacheDef.getKeyArgs ? cacheDef.getKeyArgs(args) : args;
const key = getCacheKey(cacheDef.name, cacheArgs);

await revalidate(key, signal, ...args);
const result = await revalidate(key, signal, ...args);
return result.data;
};

cacheFn.hasInMemory = async (...args: Args) => {
Expand Down

0 comments on commit e09f747

Please sign in to comment.