mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #502 from acelaya-forks/feature/forward-query
Feature/forward query
This commit is contained in:
commit
f4908cacc3
9 changed files with 119 additions and 48 deletions
|
@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
## [Unreleased]
|
||||
### Added
|
||||
* [#496](https://github.com/shlinkio/shlink-web-client/issues/496) Allowed to select "all visits" as the default interval for visits.
|
||||
* [#500](https://github.com/shlinkio/shlink-web-client/issues/500) Allowed to set the `forwardQuery` flag when creating/editing short URLs on a Shlink v2.9.0 server.
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
|
|
@ -14,8 +14,8 @@ const tagFilteringModeText = (tagFilteringMode: TagFilteringMode | undefined): s
|
|||
tagFilteringMode === 'includes' ? 'Suggest tags including input' : 'Suggest tags starting with input';
|
||||
const tagFilteringModeHint = (tagFilteringMode: TagFilteringMode | undefined): ReactNode =>
|
||||
tagFilteringMode === 'includes'
|
||||
? <>The list of suggested tags will contain existing ones <b>including</b> provided input.</>
|
||||
: <>The list of suggested tags will contain existing ones <b>starting with</b> provided input.</>;
|
||||
? <>The list of suggested tags will contain those <b>including</b> provided input.</>
|
||||
: <>The list of suggested tags will contain those <b>starting with</b> provided input.</>;
|
||||
|
||||
export const ShortUrlCreation: FC<ShortUrlCreationProps> = ({ settings, setShortUrlCreationSettings }) => {
|
||||
const shortUrlCreation: ShortUrlCreationSettings = settings.shortUrlCreation ?? { validateUrls: false };
|
||||
|
@ -24,19 +24,31 @@ export const ShortUrlCreation: FC<ShortUrlCreationProps> = ({ settings, setShort
|
|||
);
|
||||
|
||||
return (
|
||||
<SimpleCard title="Short URLs creation" className="h-100">
|
||||
<SimpleCard title="Short URLs form" className="h-100">
|
||||
<FormGroup>
|
||||
<ToggleSwitch
|
||||
checked={shortUrlCreation.validateUrls ?? false}
|
||||
onChange={(validateUrls) => setShortUrlCreationSettings({ ...shortUrlCreation, validateUrls })}
|
||||
>
|
||||
By default, request validation on long URLs when creating new short URLs.
|
||||
Request validation on long URLs when creating new short URLs.
|
||||
<small className="form-text text-muted">
|
||||
The initial state of the <b>Validate URL</b> checkbox will
|
||||
be <b>{shortUrlCreation.validateUrls ? 'checked' : 'unchecked'}</b>.
|
||||
</small>
|
||||
</ToggleSwitch>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ToggleSwitch
|
||||
checked={shortUrlCreation.forwardQuery ?? true}
|
||||
onChange={(forwardQuery) => setShortUrlCreationSettings({ ...shortUrlCreation, forwardQuery })}
|
||||
>
|
||||
Make all new short URLs forward their query params to the long URL.
|
||||
<small className="form-text text-muted">
|
||||
The initial state of the <b>Forward query params on redirect</b> checkbox will
|
||||
be <b>{shortUrlCreation.forwardQuery ?? true ? 'checked' : 'unchecked'}</b>.
|
||||
</small>
|
||||
</ToggleSwitch>
|
||||
</FormGroup>
|
||||
<FormGroup className="mb-0">
|
||||
<label>Tag suggestions search mode:</label>
|
||||
<DropdownBtn text={tagFilteringModeText(shortUrlCreation.tagFilteringMode)}>
|
||||
|
|
|
@ -22,6 +22,7 @@ export type TagFilteringMode = 'startsWith' | 'includes';
|
|||
export interface ShortUrlCreationSettings {
|
||||
validateUrls: boolean;
|
||||
tagFilteringMode?: TagFilteringMode;
|
||||
forwardQuery?: boolean;
|
||||
}
|
||||
|
||||
export type TagsMode = 'cards' | 'list';
|
||||
|
|
|
@ -30,6 +30,7 @@ const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => (
|
|||
maxVisits: undefined,
|
||||
findIfExists: false,
|
||||
validateUrl: settings?.validateUrls ?? false,
|
||||
forwardQuery: settings?.forwardQuery ?? true,
|
||||
});
|
||||
|
||||
const CreateShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>, CreateShortUrlResult: FC<CreateShortUrlResultProps>) => ({
|
||||
|
|
|
@ -42,6 +42,7 @@ const getInitialState = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSetting
|
|||
validUntil: shortUrl.meta.validUntil ?? undefined,
|
||||
maxVisits: shortUrl.meta.maxVisits ?? undefined,
|
||||
crawlable: shortUrl.crawlable,
|
||||
forwardQuery: shortUrl.forwardQuery,
|
||||
validateUrl,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import { isEmpty, pipe, replace, trim } from 'ramda';
|
|||
import classNames from 'classnames';
|
||||
import { parseISO } from 'date-fns';
|
||||
import DateInput, { DateInputProps } from '../utils/DateInput';
|
||||
import { supportsCrawlableVisits, supportsShortUrlTitle } from '../utils/helpers/features';
|
||||
import { supportsCrawlableVisits, supportsForwardQuery, supportsShortUrlTitle } from '../utils/helpers/features';
|
||||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { handleEventPreventingDefault, hasValue } from '../utils/utils';
|
||||
import Checkbox from '../utils/Checkbox';
|
||||
|
@ -33,6 +33,7 @@ export interface ShortUrlFormProps {
|
|||
|
||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||
const toDate = (date?: string | Date): Date | undefined => typeof date === 'string' ? parseISO(date) : date;
|
||||
const dynamicColClasses = (flag: boolean) => ({ 'col-sm-6': flag, 'col-sm-12': !flag });
|
||||
|
||||
export const ShortUrlForm = (
|
||||
TagsSelector: FC<TagsSelectorProps>,
|
||||
|
@ -98,11 +99,11 @@ export const ShortUrlForm = (
|
|||
|
||||
const supportsTitle = supportsShortUrlTitle(selectedServer);
|
||||
const showCustomizeCard = supportsTitle || !isEdit;
|
||||
const limitAccessCardClasses = classNames('mb-3', {
|
||||
'col-sm-6': showCustomizeCard,
|
||||
'col-sm-12': !showCustomizeCard,
|
||||
});
|
||||
const limitAccessCardClasses = classNames('mb-3', dynamicColClasses(showCustomizeCard));
|
||||
const showCrawlableControl = supportsCrawlableVisits(selectedServer);
|
||||
const showForwardQueryControl = supportsForwardQuery(selectedServer);
|
||||
const showBehaviorCard = showCrawlableControl || showForwardQueryControl;
|
||||
const extraChecksCardClasses = classNames('mb-3', dynamicColClasses(showBehaviorCard));
|
||||
|
||||
return (
|
||||
<form className="short-url-form" onSubmit={submit}>
|
||||
|
@ -154,37 +155,56 @@ export const ShortUrlForm = (
|
|||
</div>
|
||||
</Row>
|
||||
|
||||
<SimpleCard title="Extra checks" className="mb-3">
|
||||
<ShortUrlFormCheckboxGroup
|
||||
infoTooltip="If checked, Shlink will try to reach the long URL, failing in case it's not publicly accessible."
|
||||
checked={shortUrlData.validateUrl}
|
||||
onChange={(validateUrl) => setShortUrlData({ ...shortUrlData, validateUrl })}
|
||||
>
|
||||
Validate URL
|
||||
</ShortUrlFormCheckboxGroup>
|
||||
{showCrawlableControl && (
|
||||
<ShortUrlFormCheckboxGroup
|
||||
infoTooltip="This short URL will be included in the robots.txt for your Shlink instance, allowing web crawlers (like Google) to index it."
|
||||
checked={shortUrlData.crawlable}
|
||||
onChange={(crawlable) => setShortUrlData({ ...shortUrlData, crawlable })}
|
||||
>
|
||||
Make it crawlable
|
||||
</ShortUrlFormCheckboxGroup>
|
||||
)}
|
||||
{!isEdit && (
|
||||
<p>
|
||||
<Checkbox
|
||||
inline
|
||||
className="mr-2"
|
||||
checked={shortUrlData.findIfExists}
|
||||
onChange={(findIfExists) => setShortUrlData({ ...shortUrlData, findIfExists })}
|
||||
<Row>
|
||||
<div className={extraChecksCardClasses}>
|
||||
<SimpleCard title="Extra checks">
|
||||
<ShortUrlFormCheckboxGroup
|
||||
infoTooltip="If checked, Shlink will try to reach the long URL, failing in case it's not publicly accessible."
|
||||
checked={shortUrlData.validateUrl}
|
||||
onChange={(validateUrl) => setShortUrlData({ ...shortUrlData, validateUrl })}
|
||||
>
|
||||
Use existing URL if found
|
||||
</Checkbox>
|
||||
<UseExistingIfFoundInfoIcon />
|
||||
</p>
|
||||
Validate URL
|
||||
</ShortUrlFormCheckboxGroup>
|
||||
{!isEdit && (
|
||||
<p>
|
||||
<Checkbox
|
||||
inline
|
||||
className="mr-2"
|
||||
checked={shortUrlData.findIfExists}
|
||||
onChange={(findIfExists) => setShortUrlData({ ...shortUrlData, findIfExists })}
|
||||
>
|
||||
Use existing URL if found
|
||||
</Checkbox>
|
||||
<UseExistingIfFoundInfoIcon />
|
||||
</p>
|
||||
)}
|
||||
</SimpleCard>
|
||||
</div>
|
||||
{showBehaviorCard && (
|
||||
<div className="col-sm-6 mb-3">
|
||||
<SimpleCard title="Configure behavior">
|
||||
{showCrawlableControl && (
|
||||
<ShortUrlFormCheckboxGroup
|
||||
infoTooltip="This short URL will be included in the robots.txt for your Shlink instance, allowing web crawlers (like Google) to index it."
|
||||
checked={shortUrlData.crawlable}
|
||||
onChange={(crawlable) => setShortUrlData({ ...shortUrlData, crawlable })}
|
||||
>
|
||||
Make it crawlable
|
||||
</ShortUrlFormCheckboxGroup>
|
||||
)}
|
||||
{showForwardQueryControl && (
|
||||
<ShortUrlFormCheckboxGroup
|
||||
infoTooltip="When this short URL is visited, any query params appended to it will be forwarded to the long URL."
|
||||
checked={shortUrlData.forwardQuery}
|
||||
onChange={(forwardQuery) => setShortUrlData({ ...shortUrlData, forwardQuery })}
|
||||
>
|
||||
Forward query params on redirect
|
||||
</ShortUrlFormCheckboxGroup>
|
||||
)}
|
||||
</SimpleCard>
|
||||
</div>
|
||||
)}
|
||||
</SimpleCard>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface EditShortUrlData {
|
|||
maxVisits?: number | null;
|
||||
validateUrl?: boolean;
|
||||
crawlable?: boolean;
|
||||
forwardQuery?: boolean;
|
||||
}
|
||||
|
||||
export interface ShortUrlData extends EditShortUrlData {
|
||||
|
@ -30,6 +31,7 @@ export interface ShortUrl {
|
|||
domain: string | null;
|
||||
title?: string | null;
|
||||
crawlable?: boolean;
|
||||
forwardQuery?: boolean;
|
||||
}
|
||||
|
||||
export interface ShortUrlMeta {
|
||||
|
|
|
@ -21,3 +21,5 @@ export const supportsCrawlableVisits = supportsBotVisits;
|
|||
export const supportsQrErrorCorrection = serverMatchesVersions({ minVersion: '2.8.0' });
|
||||
|
||||
export const supportsDomainRedirects = supportsQrErrorCorrection;
|
||||
|
||||
export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' });
|
||||
|
|
|
@ -29,20 +29,42 @@ describe('<ShortUrlCreation />', () => {
|
|||
[ undefined, false ],
|
||||
])('URL validation switch is toggled if option is true', (shortUrlCreation, expectedChecked) => {
|
||||
const wrapper = createWrapper(shortUrlCreation);
|
||||
const toggle = wrapper.find(ToggleSwitch);
|
||||
const urlValidationToggle = wrapper.find(ToggleSwitch).first();
|
||||
|
||||
expect(toggle.prop('checked')).toEqual(expectedChecked);
|
||||
expect(urlValidationToggle.prop('checked')).toEqual(expectedChecked);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ validateUrls: true }, 'checkbox will be checked' ],
|
||||
[{ validateUrls: false }, 'checkbox will be unchecked' ],
|
||||
[ undefined, 'checkbox will be unchecked' ],
|
||||
[{ forwardQuery: true }, true ],
|
||||
[{ forwardQuery: false }, false ],
|
||||
[{}, true ],
|
||||
])('forward query switch is toggled if option is true', (shortUrlCreation, expectedChecked) => {
|
||||
const wrapper = createWrapper({ validateUrls: true, ...shortUrlCreation });
|
||||
const forwardQueryToggle = wrapper.find(ToggleSwitch).last();
|
||||
|
||||
expect(forwardQueryToggle.prop('checked')).toEqual(expectedChecked);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ validateUrls: true }, 'Validate URL checkbox will be checked' ],
|
||||
[{ validateUrls: false }, 'Validate URL checkbox will be unchecked' ],
|
||||
[ undefined, 'Validate URL checkbox will be unchecked' ],
|
||||
])('shows expected helper text for URL validation', (shortUrlCreation, expectedText) => {
|
||||
const wrapper = createWrapper(shortUrlCreation);
|
||||
const text = wrapper.find('.form-text').first();
|
||||
const validateUrlText = wrapper.find('.form-text').first();
|
||||
|
||||
expect(text.text()).toContain(expectedText);
|
||||
expect(validateUrlText.text()).toContain(expectedText);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ forwardQuery: true }, 'Forward query params on redirect checkbox will be checked' ],
|
||||
[{ forwardQuery: false }, 'Forward query params on redirect checkbox will be unchecked' ],
|
||||
[{}, 'Forward query params on redirect checkbox will be checked' ],
|
||||
])('shows expected helper text for query forwarding', (shortUrlCreation, expectedText) => {
|
||||
const wrapper = createWrapper({ validateUrls: true, ...shortUrlCreation });
|
||||
const forwardQueryText = wrapper.find('.form-text').at(1);
|
||||
|
||||
expect(forwardQueryText.text()).toContain(expectedText);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -62,15 +84,24 @@ describe('<ShortUrlCreation />', () => {
|
|||
expect(hintText.text()).toContain(expectedHint);
|
||||
});
|
||||
|
||||
it.each([[ true ], [ false ]])('invokes setShortUrlCreationSettings when toggle value changes', (validateUrls) => {
|
||||
it.each([[ true ], [ false ]])('invokes setShortUrlCreationSettings when URL validation toggle value changes', (validateUrls) => {
|
||||
const wrapper = createWrapper();
|
||||
const toggle = wrapper.find(ToggleSwitch);
|
||||
const urlValidationToggle = wrapper.find(ToggleSwitch).first();
|
||||
|
||||
expect(setShortUrlCreationSettings).not.toHaveBeenCalled();
|
||||
toggle.simulate('change', validateUrls);
|
||||
urlValidationToggle.simulate('change', validateUrls);
|
||||
expect(setShortUrlCreationSettings).toHaveBeenCalledWith({ validateUrls });
|
||||
});
|
||||
|
||||
it.each([[ true ], [ false ]])('invokes setShortUrlCreationSettings when forward query toggle value changes', (forwardQuery) => {
|
||||
const wrapper = createWrapper();
|
||||
const urlValidationToggle = wrapper.find(ToggleSwitch).last();
|
||||
|
||||
expect(setShortUrlCreationSettings).not.toHaveBeenCalled();
|
||||
urlValidationToggle.simulate('change', forwardQuery);
|
||||
expect(setShortUrlCreationSettings).toHaveBeenCalledWith(expect.objectContaining({ forwardQuery }));
|
||||
});
|
||||
|
||||
it('invokes setShortUrlCreationSettings when dropdown value changes', () => {
|
||||
const wrapper = createWrapper();
|
||||
const firstDropdownItem = wrapper.find(DropdownItem).first();
|
||||
|
|
Loading…
Reference in a new issue