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

Notify Administrators when there is a new version of Praise out #522

Merged
merged 7 commits into from
Jul 26, 2022
Merged
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: 0 additions & 6 deletions docker-compose.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ services:
links:
- api

watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 1800

volumes:
mongodb_data:
caddy_data:
Expand Down
6 changes: 0 additions & 6 deletions docker-compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ services:
links:
- api

watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 43200

volumes:
mongodb_data:
caddy_data:
Expand Down
7 changes: 7 additions & 0 deletions packages/frontend/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ REACT_APP_SERVER_URL=https://localhost

# Port number used when running frontend for development, outside of Docker
PORT=3000

# App version from package.json
REACT_APP_VERSION=$npm_package_version

# Github repo info
REACT_APP_GITHUB_REPO_OWNER=commons-stack
REACT_APP_GITHUB_REPO_NAME=praise
27 changes: 26 additions & 1 deletion packages/frontend/src/layouts/AuthenticatedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import { useRecoilValue } from 'recoil';
import { SingleSetting, useAllSettingsQuery } from '@/model/settings';
import { useAllPeriodsQuery } from '@/model/periods';
import { useAllUsersQuery } from '@/model/users';
import { ActiveUserRoles } from '@/model/auth';
import { ActiveUserRoles, HasRole, ROLE_ADMIN } from '@/model/auth';
import { Nav } from '@/navigation/Nav';
import { AuthenticatedRoutes } from '@/navigation/AuthenticatedRoutes';
import { usePraiseAppVersion } from '@/model/app';

export const AuthenticatedLayout = (): JSX.Element | null => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const siteNameSetting = useRecoilValue(SingleSetting('NAME'));
const activeUserRoles = useRecoilValue(ActiveUserRoles);
const isAdmin = useRecoilValue(HasRole(ROLE_ADMIN));
const appVersion = usePraiseAppVersion();

useAllPeriodsQuery();
useAllSettingsQuery();
useAllUsersQuery();
Expand Down Expand Up @@ -83,6 +87,27 @@ export const AuthenticatedLayout = (): JSX.Element | null => {
</Dialog>
</Transition.Root>

{isAdmin && appVersion.newVersionAvailable && (
<div className="sticky top-0 p-3 text-center bg-opacity-50 bg-warm-gray-100 lg:pl-64">
<p>
🎉 There is a new version of the Praise out! You are running{' '}
{appVersion.current}, latest version is {appVersion.latest}.{' '}
<a
href="https://github.com/commons-stack/praise/releases"
className="font-bold"
target="_blank"
rel="noreferrer"
>
Release notes
</a>
</p>
</div>
)}

<div className="fixed bottom-0 right-0 invisible p-1 text-xs text-right lg:visible">
{appVersion.current}
</div>

{/* Static sidebar for desktop */}
<div className="hidden w-64 lg:flex lg:flex-col lg:fixed lg:inset-y-0">
{/* Sidebar component, swap this element with another sidebar if you like */}
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/model/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'recoil';
import { makeApiAuthClient } from '../utils/api';

type RequestParams = {
export type RequestParams = {
[key: string]: SerializableParam;
url: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
44 changes: 44 additions & 0 deletions packages/frontend/src/model/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { AxiosResponse } from 'axios';
import { selector, useRecoilValue } from 'recoil';
import { isResponseOk } from './api';
import { ExternalGet } from './axios';

export interface GithubResponse {
name: string;
}

export const GithubVersionQuery = selector({
key: 'GithubVersionQuery',
get: ({ get }): AxiosResponse<GithubResponse> => {
const repoOwner = process.env.REACT_APP_GITHUB_REPO_OWNER;
const repoName = process.env.REACT_APP_GITHUB_REPO_NAME;

return get(
ExternalGet({
url: `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`,
})
) as AxiosResponse<GithubResponse>;
},
});

interface PraiseAppVersion {
current: string | undefined;
latest: string | undefined;
newVersionAvailable: boolean;
}

export const usePraiseAppVersion = (): PraiseAppVersion => {
const appVersion: PraiseAppVersion = {
current: process.env.REACT_APP_VERSION,
latest: undefined,
newVersionAvailable: false,
};

const response = useRecoilValue(GithubVersionQuery);
if (isResponseOk(response)) {
appVersion.latest = (response.data as GithubResponse).name.substring(1);
appVersion.newVersionAvailable = appVersion.latest !== appVersion.current;
}

return appVersion;
};
22 changes: 22 additions & 0 deletions packages/frontend/src/model/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AxiosResponse } from 'axios';
import { selectorFamily } from 'recoil';
import { makeClient } from '@/utils/axios';
import { RequestParams } from './api';

/**
* External GET request
*/
export const ExternalGet = selectorFamily<
AxiosResponse<unknown>,
RequestParams
>({
key: 'ExternalGet',
get: (params: RequestParams) => async (): Promise<AxiosResponse<unknown>> => {
const { config, url } = params;

const client = makeClient();
const response = await client.get(url, config);

return response;
},
});
2 changes: 1 addition & 1 deletion packages/frontend/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@apply dark:border-slate-700;
}
#root {
@apply min-h-screen text-sm text-warm-gray-800 dark:bg-slate-800 dark:text-white bg-warm-gray-300;
@apply min-h-screen text-sm text-warm-gray-800 dark:bg-slate-800 dark:text-white bg-warm-gray-200;
}

h2 {
Expand Down
28 changes: 1 addition & 27 deletions packages/frontend/src/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import axios, { AxiosError, AxiosInstance } from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import { toast } from 'react-hot-toast';
import { getRecoil } from 'recoil-nexus';
import { ActiveTokenSet } from '@/model/auth';
import { requestApiAuthRefresh } from './auth';
import { handleErrors } from './axios';

/**
* Attempt to refresh auth token and retry request
Expand All @@ -19,32 +19,6 @@ const refreshAuthTokenSet = async (err: AxiosError): Promise<void> => {
] = `Bearer ${tokenSet.accessToken}`;
};

/**
* Handle error responses (excluding initial 401 response)
*
* @param err
*/
const handleErrors = (err: AxiosError): void => {
// Any HTTP Code which is not 2xx will be considered as error
const statusCode = err?.response?.status;

if (err?.request && !err?.response) {
toast.error('Server did not respond');
} else if (statusCode === 404) {
window.location.href = '/404';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
} else if ([403, 400].includes(statusCode) && err?.response?.data?.message) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
toast.error(err.response.data.message);
} else if (statusCode === 401) {
window.location.href = '/';
} else {
toast.error('Unknown Error');
}
};

/**
* We assume the API to be running on the same domain in production currently.
* Why? The frontend is built as a static website and cannot easily accept
Expand Down
44 changes: 44 additions & 0 deletions packages/frontend/src/utils/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import axios, { AxiosError, AxiosInstance } from 'axios';
import { toast } from 'react-hot-toast';

/**
* Handle error responses (excluding initial 401 response)
*
* @param err
*/
export const handleErrors = (err: AxiosError): void => {
// Any HTTP Code which is not 2xx will be considered as error
const statusCode = err?.response?.status;

if (err?.request && !err?.response) {
toast.error('Server did not respond');
} else if (statusCode === 404) {
window.location.href = '/404';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
} else if ([403, 400].includes(statusCode) && err?.response?.data?.message) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
toast.error(err.response.data.message);
} else if (statusCode === 401) {
window.location.href = '/';
} else {
toast.error('Unknown Error');
}
};

/**
* Client for external requests.
* @returns
*/
export const makeClient = (): AxiosInstance => {
const client = axios.create();

client.interceptors.response.use(
(res) => res,
(err) => {
return handleErrors(err);
}
);
return client;
};