diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad769c4..6fcd8727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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* diff --git a/src/settings/ShortUrlCreation.tsx b/src/settings/ShortUrlCreation.tsx index 2caaa329..53814d71 100644 --- a/src/settings/ShortUrlCreation.tsx +++ b/src/settings/ShortUrlCreation.tsx @@ -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 including provided input. - : <>The list of suggested tags will contain existing ones starting with provided input.; + ? <>The list of suggested tags will contain those including provided input. + : <>The list of suggested tags will contain those starting with provided input.; export const ShortUrlCreation: FC = ({ settings, setShortUrlCreationSettings }) => { const shortUrlCreation: ShortUrlCreationSettings = settings.shortUrlCreation ?? { validateUrls: false }; @@ -24,19 +24,31 @@ export const ShortUrlCreation: FC = ({ settings, setShort ); return ( - + 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. The initial state of the Validate URL checkbox will be {shortUrlCreation.validateUrls ? 'checked' : 'unchecked'}. + + setShortUrlCreationSettings({ ...shortUrlCreation, forwardQuery })} + > + Make all new short URLs forward their query params to the long URL. + + The initial state of the Forward query params on redirect checkbox will + be {shortUrlCreation.forwardQuery ?? true ? 'checked' : 'unchecked'}. + + + diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts index 3fb0ca3d..6b0079d2 100644 --- a/src/settings/reducers/settings.ts +++ b/src/settings/reducers/settings.ts @@ -22,6 +22,7 @@ export type TagFilteringMode = 'startsWith' | 'includes'; export interface ShortUrlCreationSettings { validateUrls: boolean; tagFilteringMode?: TagFilteringMode; + forwardQuery?: boolean; } export type TagsMode = 'cards' | 'list'; diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 68d28125..a13daab8 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -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, CreateShortUrlResult: FC) => ({ diff --git a/src/short-urls/EditShortUrl.tsx b/src/short-urls/EditShortUrl.tsx index 56151bfa..0ecba149 100644 --- a/src/short-urls/EditShortUrl.tsx +++ b/src/short-urls/EditShortUrl.tsx @@ -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, }; }; diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index b09a70cb..4534f514 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -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, @@ -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 (
@@ -154,37 +155,56 @@ export const ShortUrlForm = ( - - setShortUrlData({ ...shortUrlData, validateUrl })} - > - Validate URL - - {showCrawlableControl && ( - setShortUrlData({ ...shortUrlData, crawlable })} - > - Make it crawlable - - )} - {!isEdit && ( -

- setShortUrlData({ ...shortUrlData, findIfExists })} + +

+ + setShortUrlData({ ...shortUrlData, validateUrl })} > - Use existing URL if found - - -

+ Validate URL +
+ {!isEdit && ( +

+ setShortUrlData({ ...shortUrlData, findIfExists })} + > + Use existing URL if found + + +

+ )} +
+
+ {showBehaviorCard && ( +
+ + {showCrawlableControl && ( + setShortUrlData({ ...shortUrlData, crawlable })} + > + Make it crawlable + + )} + {showForwardQueryControl && ( + setShortUrlData({ ...shortUrlData, forwardQuery })} + > + Forward query params on redirect + + )} + +
)} -
+ )} diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index c0fded8b..39e2ea39 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -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 { diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 120926f8..d424e512 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -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' }); diff --git a/test/settings/ShortUrlCreation.test.tsx b/test/settings/ShortUrlCreation.test.tsx index 3a61f5dd..fda31fdb 100644 --- a/test/settings/ShortUrlCreation.test.tsx +++ b/test/settings/ShortUrlCreation.test.tsx @@ -29,20 +29,42 @@ describe('', () => { [ 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('', () => { 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();