mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 06:04:05 +00:00
refactor: simplify url preset logic
This commit is contained in:
committed by
Carlos Valente
parent
430bfb38f2
commit
f0e6231338
@@ -1,8 +1,8 @@
|
|||||||
import { resolvePath } from 'react-router-dom';
|
import { resolvePath } from 'react-router-dom';
|
||||||
|
|
||||||
import { generateUrlFromPreset, getRouteFromPreset, validateUrlPresetPath } from '../urlPresets';
|
import { generatePathFromPreset, getRouteFromPreset, validateUrlPresetPath } from '../urlPresets';
|
||||||
|
|
||||||
describe('A preset fails if incorrect', () => {
|
describe('validateUrlPresetPaths()', () => {
|
||||||
test.each([
|
test.each([
|
||||||
// no empty
|
// no empty
|
||||||
'',
|
'',
|
||||||
@@ -17,87 +17,56 @@ describe('A preset fails if incorrect', () => {
|
|||||||
// no editor
|
// no editor
|
||||||
'editor',
|
'editor',
|
||||||
'editor?test',
|
'editor?test',
|
||||||
])('validateUrlPresetPath(%s) should return false', (t) => {
|
])('flags known edge cases: %s', (t) => {
|
||||||
expect(validateUrlPresetPath(t).isValid).toBeFalsy();
|
expect(validateUrlPresetPath(t).isValid).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateUrlFromPreset and getRouteFromPreset function', () => {
|
describe('getRouteFromPreset()', () => {
|
||||||
test('generate the expected url from an alias', () => {
|
const presets = [
|
||||||
const testData = [
|
{
|
||||||
{
|
enabled: true,
|
||||||
enabled: true,
|
alias: 'demopage',
|
||||||
alias: 'demopage',
|
pathAndParams: '/timer?user=guest',
|
||||||
pathAndParams: '/timer?user=guest',
|
},
|
||||||
},
|
];
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
it('checks if the current location matches an enabled preset', () => {
|
||||||
{
|
// we make the current location be the alias
|
||||||
url: '/timer?user=guest&alias=demopage',
|
const location = resolvePath('demopage');
|
||||||
},
|
expect(getRouteFromPreset(location, presets)).toStrictEqual('timer?user=guest&alias=demopage');
|
||||||
];
|
|
||||||
|
|
||||||
expect(generateUrlFromPreset(testData[0])).toStrictEqual(expected[0].url);
|
|
||||||
});
|
});
|
||||||
test('generate the url to redirect to when the current URL is just the alias', () => {
|
|
||||||
const presets = [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
alias: 'demopage',
|
|
||||||
pathAndParams: '/timer?user=guest',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// let current location be the alias
|
|
||||||
const location = resolvePath(presets[0].alias);
|
|
||||||
|
|
||||||
const expected = [
|
it('returns null if the current location is the exact match of an unwrapped alias', () => {
|
||||||
{
|
// we make the current location be the alias
|
||||||
url: '/timer?user=guest&alias=demopage',
|
const location = resolvePath('/timer?user=guest&alias=demopage');
|
||||||
},
|
expect(getRouteFromPreset(location, presets)).toEqual(null);
|
||||||
];
|
|
||||||
|
|
||||||
// @ts-expect-error -- using a Path as a Location
|
|
||||||
expect(getRouteFromPreset(location, presets, null)).toStrictEqual(expected[0].url);
|
|
||||||
});
|
});
|
||||||
test('generate the url to redirect to when the current URL the same url but with a change of params', () => {
|
|
||||||
const presets = [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
alias: 'demopage',
|
|
||||||
pathAndParams: '/timer?user=guest',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// let current location be the actual url with alias attached to it
|
|
||||||
const location = resolvePath(presets[0].pathAndParams);
|
|
||||||
const urlSearchParams = new URLSearchParams(location.search);
|
|
||||||
urlSearchParams.append('alias', presets[0].alias); //
|
|
||||||
|
|
||||||
// update current alias with extra param
|
it('returns a new destination if the current location is an out-of-date unwrapped alias', () => {
|
||||||
presets[0].pathAndParams += '&eventId=674';
|
// we make the current location be the alias
|
||||||
const expected = [
|
const location = resolvePath('/timer?user=admin&alias=demopage');
|
||||||
{
|
expect(getRouteFromPreset(location, presets)).toEqual('timer?user=guest&alias=demopage');
|
||||||
url: '/timer?user=guest&eventId=674&alias=demopage',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// @ts-expect-error -- using a Path as a Location
|
|
||||||
expect(getRouteFromPreset(location, presets, urlSearchParams)).toStrictEqual(expected[0].url);
|
|
||||||
});
|
});
|
||||||
test('generate no url to redirect to when the current URL the same url', () => {
|
|
||||||
const presets = [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
alias: 'demopage',
|
|
||||||
pathAndParams: '/timer?user=guest',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// let current location be the actual url with alias attached to it
|
|
||||||
const location = resolvePath(presets[0].pathAndParams);
|
|
||||||
const urlSearchParams = new URLSearchParams(location.search);
|
|
||||||
urlSearchParams.append('alias', presets[0].alias);
|
|
||||||
|
|
||||||
// @ts-expect-error -- using a Path as a Location
|
it('checks if the current location contains an unwrapped preset', () => {
|
||||||
expect(getRouteFromPreset(location, presets, urlSearchParams)).toBeNull();
|
// we make the current location be the alias
|
||||||
|
const location = resolvePath('/timer?user=guest&alias=demopage');
|
||||||
|
expect(getRouteFromPreset(location, presets)).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores a location that has no presets', () => {
|
||||||
|
// we make the current location be the alias
|
||||||
|
const location = resolvePath('/unknown');
|
||||||
|
expect(getRouteFromPreset(location, presets)).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generatePathFromPreset()', () => {
|
||||||
|
test.each([
|
||||||
|
['timer?user=guest', 'demopage', 'timer?user=guest&alias=demopage'],
|
||||||
|
['timer?user=admin', 'demopage', 'timer?user=admin&alias=demopage'],
|
||||||
|
])('generates a path from a preset: %s', (path, alias, expected) => {
|
||||||
|
expect(generatePathFromPreset(path, alias)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import isEqual from 'react-fast-compare';
|
import { Path, resolvePath } from 'react-router-dom';
|
||||||
import { Location, resolvePath } from 'react-router-dom';
|
|
||||||
import { URLPreset } from 'ontime-types';
|
import { URLPreset } from 'ontime-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a preset against defined parameters
|
* Validates a preset against defined parameters
|
||||||
* @param {string} preset
|
* Used in the context of form validation
|
||||||
* @returns {{message: string, isValid: boolean}}
|
|
||||||
*/
|
*/
|
||||||
export const validateUrlPresetPath = (preset: string): { message: string; isValid: boolean } => {
|
export function validateUrlPresetPath(preset: string): { message: string; isValid: boolean } {
|
||||||
if (preset === '' || preset == null) {
|
if (preset === '' || preset == null) {
|
||||||
return { isValid: false, message: 'Path cannot be empty' };
|
return { isValid: false, message: 'Path cannot be empty' };
|
||||||
}
|
}
|
||||||
@@ -26,7 +24,7 @@ export const validateUrlPresetPath = (preset: string): { message: string; isVali
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { isValid: true, message: 'ok' };
|
return { isValid: true, message: 'ok' };
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility removes trailing slash from a string
|
* Utility removes trailing slash from a string
|
||||||
@@ -36,48 +34,52 @@ function removeTrailingSlash(text: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the URL to send a preset to
|
* Checks whether the current location corresponds to a preset and returns the new path if necessary
|
||||||
* @param location
|
|
||||||
* @param data
|
|
||||||
* @param searchParams
|
|
||||||
*/
|
*/
|
||||||
export const getRouteFromPreset = (location: Location, data: URLPreset[], searchParams: URLSearchParams) => {
|
export function getRouteFromPreset(location: Path, urlPresets: URLPreset[]) {
|
||||||
|
// current url is the pathname without the leading slash
|
||||||
const currentURL = location.pathname.substring(1);
|
const currentURL = location.pathname.substring(1);
|
||||||
|
|
||||||
// we need to check if the whole url here is an alias, so we can redirect
|
// we need to check if the whole url is an alias
|
||||||
const foundPreset = data.find((preset) => preset.alias === removeTrailingSlash(currentURL) && preset.enabled);
|
const foundPreset = urlPresets.find((preset) => preset.alias === removeTrailingSlash(currentURL) && preset.enabled);
|
||||||
if (foundPreset) {
|
if (foundPreset) {
|
||||||
return generateUrlFromPreset(foundPreset);
|
// if so, we can redirect to the preset path
|
||||||
|
return generatePathFromPreset(foundPreset.pathAndParams, foundPreset.alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the current url is not an alias, we check if the alias is in the search parameters
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
|
||||||
const presetOnPage = searchParams.get('alias');
|
const presetOnPage = searchParams.get('alias');
|
||||||
for (const d of data) {
|
if (!presetOnPage) {
|
||||||
if (presetOnPage) {
|
return null;
|
||||||
// if the alias fits the preset on this page, but the URL is different, we redirect user to the new URL
|
}
|
||||||
// if we have the same alias and its enabled and its not empty
|
|
||||||
if (d.alias !== '' && d.enabled && d.alias === presetOnPage) {
|
for (const preset of urlPresets) {
|
||||||
const newPath = resolvePath(d.pathAndParams);
|
// if the page has a known enabled alias, we check if we need to redirect
|
||||||
const urlParams = new URLSearchParams(newPath.search);
|
if (preset.alias === presetOnPage && preset.enabled) {
|
||||||
urlParams.set('alias', d.alias);
|
const newPath = generatePathFromPreset(preset.pathAndParams, preset.alias);
|
||||||
// we confirm either the url parameters does not match or the url path doesnt
|
const currentPath = `${location.pathname}${location.search}`.substring(1);
|
||||||
if (!isEqual(urlParams, searchParams) || newPath.pathname !== location.pathname) {
|
if (currentPath !== newPath) {
|
||||||
// we then redirect to the alias route, since the view listening to this alias has an outdated URL
|
// if current path is out of date
|
||||||
return `${newPath.pathname}?${urlParams}`;
|
// return new path so we can redirect
|
||||||
}
|
return newPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate URL from an preset
|
* Handles generating a path and search parameters from a preset
|
||||||
* @param presetData
|
|
||||||
*/
|
*/
|
||||||
export const generateUrlFromPreset = (presetData: URLPreset) => {
|
export function generatePathFromPreset(pathAndParams: string, alias: string): string {
|
||||||
const newPresetPath = resolvePath(presetData.pathAndParams);
|
const path = resolvePath(pathAndParams);
|
||||||
const urlParams = new URLSearchParams(newPresetPath.search);
|
const searchParams = new URLSearchParams(path.search);
|
||||||
urlParams.set('alias', presetData.alias);
|
|
||||||
|
|
||||||
return `${newPresetPath.pathname}?${urlParams}`;
|
// save the alias so we have a reference to it being a preset and can update if necessary
|
||||||
};
|
searchParams.set('alias', alias);
|
||||||
|
|
||||||
|
// return path concatenated without the leading slash
|
||||||
|
return `${path.pathname}?${searchParams}`.substring(1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { ComponentType, useEffect } from 'react';
|
import { ComponentType, useEffect } from 'react';
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import useUrlPresets from '../common/hooks-query/useUrlPresets';
|
import useUrlPresets from '../common/hooks-query/useUrlPresets';
|
||||||
import { getRouteFromPreset } from '../common/utils/urlPresets';
|
import { getRouteFromPreset } from '../common/utils/urlPresets';
|
||||||
@@ -8,19 +8,19 @@ import { getRouteFromPreset } from '../common/utils/urlPresets';
|
|||||||
const withPreset = <P extends object>(Component: ComponentType<P>) => {
|
const withPreset = <P extends object>(Component: ComponentType<P>) => {
|
||||||
return (props: Partial<P>) => {
|
return (props: Partial<P>) => {
|
||||||
const { data } = useUrlPresets();
|
const { data } = useUrlPresets();
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// navigate if is alias route
|
// navigate if is alias route
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
const url = getRouteFromPreset(location, data, searchParams);
|
const destination = getRouteFromPreset(location, data);
|
||||||
// navigate to this route if its not empty
|
|
||||||
if (url) {
|
// navigate to this destination if its not null
|
||||||
navigate(url);
|
if (destination) {
|
||||||
|
navigate(destination);
|
||||||
}
|
}
|
||||||
}, [data, searchParams, navigate, location]);
|
}, [data, navigate, location]);
|
||||||
|
|
||||||
return <Component {...(props as P)} />;
|
return <Component {...(props as P)} />;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user