Commit d808cf4d authored by Daniel Tian's avatar Daniel Tian Committed by Andrew Fontaine

Serialize and deserialize by default for LocalStorageSync component

Changelog: changed
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84159
parent 96a8605e
......@@ -57,6 +57,7 @@ export default {
:value="sortDirection"
:storage-key="storageKey"
:persist="persistSortOrder"
as-string
@input="setDiscussionSortDirection({ direction: $event })"
/>
<gl-dropdown :text="dropdownText" class="js-dropdown-text full-width-mobile">
......
......@@ -99,7 +99,6 @@ export default {
<local-storage-sync
storage-key="package_registry_list_sorting"
:value="sorting"
as-json
@input="updateSorting"
>
<url-sync>
......
......@@ -273,6 +273,7 @@ export default {
<local-storage-sync
:storage-key="$options.viewTypeKey"
:value="currentViewType"
as-string
@input="updateViewType"
>
<graph-view-selector
......
......@@ -144,7 +144,6 @@ export default {
<local-storage-sync
v-model="autoDevopsEnabledAlertDismissedProjects"
:storage-key="$options.autoDevopsEnabledAlertStorageKey"
as-json
/>
<user-callout-dismisser
......
......@@ -112,7 +112,6 @@ export default {
v-model="mergeRequestMeta"
:storage-key="$options.storageKey"
:clear="clearStorage"
as-json
/>
<edit-meta-controls
ref="editMetaControls"
......
......@@ -37,7 +37,7 @@ export default {
<template>
<div v-show="showAlert">
<local-storage-sync v-model="isDismissed" :storage-key="storageKey" as-json />
<local-storage-sync v-model="isDismissed" :storage-key="storageKey" />
<gl-alert v-if="showAlert" @dismiss="dismissFeedbackAlert">
<slot></slot>
</gl-alert>
......
<script>
import { isEqual } from 'lodash';
import { isEqual, isString } from 'lodash';
/**
* This component will save and restore a value to and from localStorage.
* The value will be saved only when the value changes; the initial value won't be saved.
*
* By default, the value will be saved using JSON.stringify(), and retrieved back using JSON.parse().
*
* If you would like to save the raw string instead, you may set the 'asString' prop to true, though be aware that this is a
* legacy prop to maintain backwards compatibility.
*
* For new components saving data for the first time, it's recommended to not use 'asString' even if you're saving a string; it will still be
* saved and restored properly using JSON.stringify()/JSON.parse().
*/
export default {
props: {
storageKey: {
......@@ -12,7 +24,7 @@ export default {
required: false,
default: '',
},
asJson: {
asString: {
type: Boolean,
required: false,
default: false,
......@@ -30,6 +42,8 @@ export default {
},
watch: {
value(newVal) {
if (!this.persist) return;
this.saveValue(this.serialize(newVal));
},
clear(newVal) {
......@@ -67,15 +81,22 @@ export default {
}
},
saveValue(val) {
if (!this.persist) return;
localStorage.setItem(this.storageKey, val);
},
serialize(val) {
return this.asJson ? JSON.stringify(val) : val;
if (!isString(val) && this.asString) {
// eslint-disable-next-line no-console
console.warn(
`[gitlab] LocalStorageSync is saving`,
val,
`to the key "${this.storageKey}", but it is not a string and the 'asString' prop is true. This will save and restore the stringified value rather than the original value. If this is not intended, please remove or set the 'asString' prop to false.`,
);
}
return this.asString ? val : JSON.stringify(val);
},
deserialize(val) {
return this.asJson ? JSON.parse(val) : val;
return this.asString ? val : JSON.parse(val);
},
},
render() {
......
......@@ -43,7 +43,7 @@ export default {
</script>
<template>
<local-storage-sync :storage-key="storageKey" :value="selected" @input="setSelected">
<local-storage-sync :storage-key="storageKey" :value="selected" as-string @input="setSelected">
<gl-dropdown :text="dropdownText" lazy>
<gl-dropdown-item
v-for="option in parsedOptions"
......
......@@ -314,6 +314,7 @@ export default {
<local-storage-sync
storage-key="gl-web-ide-button-selected"
:value="selection"
as-string
@input="select"
/>
<gl-modal
......
......@@ -157,8 +157,8 @@ export default {
</script>
<template>
<div>
<local-storage-sync v-model="sortBy" :storage-key="$options.sortByStorageKey" as-json />
<local-storage-sync v-model="sortDesc" :storage-key="$options.sortDescStorageKey" as-json />
<local-storage-sync v-model="sortBy" :storage-key="$options.sortByStorageKey" />
<local-storage-sync v-model="sortDesc" :storage-key="$options.sortDescStorageKey" />
<h4>{{ tableHeader }}</h4>
<gl-table
:fields="tableHeaderFields"
......
......@@ -140,8 +140,8 @@ export default {
</script>
<template>
<div>
<local-storage-sync v-model="sortBy" :storage-key="$options.sortByStorageKey" as-json />
<local-storage-sync v-model="sortDesc" :storage-key="$options.sortDescStorageKey" as-json />
<local-storage-sync v-model="sortBy" :storage-key="$options.sortByStorageKey" />
<local-storage-sync v-model="sortDesc" :storage-key="$options.sortDescStorageKey" />
<gl-table
:fields="tableHeaderFields"
:items="enabledNamespaces"
......
......@@ -74,7 +74,6 @@ export default {
<local-storage-sync
v-model="userManuallyCollapsed"
:storage-key="$options.MR_APPROVALS_PROMO_DISMISSED"
as-json
/>
<template v-if="isReady">
<p class="gl-mb-0 gl-text-gray-500">
......
<script>
import { GlToggle } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
export default {
......@@ -20,9 +19,6 @@ export default {
onToggle(val) {
this.setShowLabels(val);
},
onStorageUpdate(val) {
this.setShowLabels(parseBoolean(val));
},
},
};
</script>
......@@ -30,9 +26,9 @@ export default {
<template>
<div class="board-labels-toggle-wrapper gl-display-flex gl-align-items-center gl-ml-3">
<local-storage-sync
:value="isShowingLabels"
storage-key="gl-show-board-labels"
:value="JSON.stringify(isShowingLabels)"
@input="onStorageUpdate"
@input="setShowLabels"
/>
<gl-toggle
:value="isShowingLabels"
......
......@@ -324,7 +324,6 @@ export default {
<gl-form novalidate @submit.prevent="onSubmit()">
<local-storage-sync
v-if="!isEdit"
as-json
:storage-key="storageKey"
:clear="clearStorage"
:value="formFieldValues"
......
<script>
import { difference } from 'lodash';
import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils';
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { translateScannerNames } from '~/security_configuration/utils';
......@@ -86,8 +86,8 @@ export default {
setCookie(this.$options.autoFixUserCalloutCookieName, 'true');
this.shouldShowAutoFixUserCallout = false;
},
setScannerAlertDismissed(value) {
this.scannerAlertDismissed = parseBoolean(value);
dismissScannerAlert() {
this.scannerAlertDismissed = true;
},
},
projectVulnerabilitiesQuery,
......@@ -101,15 +101,14 @@ export default {
<div v-else>
<local-storage-sync
:value="String(scannerAlertDismissed)"
v-model="scannerAlertDismissed"
:storage-key="$options.SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY"
@input="setScannerAlertDismissed"
/>
<security-scanner-alert
v-if="shouldShowScannersAlert"
:not-enabled-scanners="notEnabledSecurityScanners"
:no-pipeline-run-scanners="noPipelineRunSecurityScanners"
@dismiss="setScannerAlertDismissed('true')"
@dismiss="dismissScannerAlert"
/>
<auto-fix-user-callout
......
......@@ -262,7 +262,6 @@ export default {
<local-storage-sync
v-if="shouldShowPageSizeSelector"
v-model="pageSize"
as-json
:storage-key="$options.PAGE_SIZE_STORAGE_KEY"
>
<page-size-selector v-model="pageSize" class="gl-absolute gl-right-0" />
......
......@@ -83,7 +83,7 @@ export default {
</script>
<template>
<local-storage-sync v-model="surveyShowDate" :storage-key="storageKey">
<local-storage-sync v-model="surveyShowDate" :storage-key="storageKey" as-string>
<gl-banner
v-if="shouldShowSurvey"
:title="title"
......
......@@ -373,10 +373,7 @@ describe('Vulnerability list GraphQL component', () => {
findPageSizeSelector().vm.$emit('input', pageSize);
await nextTick();
expect(findLocalStorageSync().props()).toMatchObject({
asJson: true,
value: pageSize,
});
expect(findLocalStorageSync().props('value')).toBe(pageSize);
});
});
});
......
......@@ -15,6 +15,7 @@ describe('Shared Survey Banner component', () => {
let wrapper;
const findGlBanner = () => wrapper.findComponent(GlBanner);
const findAskLaterButton = () => wrapper.findByTestId('ask-later-button');
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const getOffsetDateString = (days) => {
const date = new Date();
......@@ -60,6 +61,7 @@ describe('Shared Survey Banner component', () => {
expect(findGlBanner().html()).toContain(description);
expect(findAskLaterButton().exists()).toBe(true);
expect(findLocalStorageSync().props('asString')).toBe(true);
expect(findGlBanner().props()).toMatchObject({
title,
buttonText,
......
......@@ -38,8 +38,8 @@ describe('Sort Discussion component', () => {
createComponent();
});
it('has local storage sync', () => {
expect(findLocalStorageSync().exists()).toBe(true);
it('has local storage sync with the correct props', () => {
expect(findLocalStorageSync().props('asString')).toBe(true);
});
it('calls setDiscussionSortDirection when update is emitted', () => {
......
......@@ -73,7 +73,6 @@ describe('Package Search', () => {
mountComponent();
expect(findLocalStorageSync().props()).toMatchObject({
asJson: true,
storageKey: 'package_registry_list_sorting',
value: {
orderBy: LIST_KEY_CREATED_AT,
......
......@@ -30,6 +30,7 @@ import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql';
import * as sentryUtils from '~/pipelines/utils';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { mockRunningPipelineHeaderData } from '../mock_data';
import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data';
......@@ -55,6 +56,7 @@ describe('Pipeline graph wrapper', () => {
wrapper.find(StageColumnComponent).findAll('[data-testid="stage-column-group"]');
const getViewSelector = () => wrapper.find(GraphViewSelector);
const getViewSelectorTrip = () => getViewSelector().findComponent(GlAlert);
const getLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const createComponent = ({
apolloProvider,
......@@ -376,6 +378,10 @@ describe('Pipeline graph wrapper', () => {
localStorage.clear();
});
it('sets the asString prop on the LocalStorageSync component', () => {
expect(getLocalStorageSync().props('asString')).toBe(true);
});
it('reads the view type from localStorage when available', () => {
const viewSelectorNeedsSegment = wrapper
.find(GlButtonGroup)
......
......@@ -36,10 +36,10 @@ describe('Persisted dropdown selection', () => {
});
describe('local storage sync', () => {
it('uses the local storage sync component', () => {
it('uses the local storage sync component with the correct props', () => {
createComponent();
expect(findLocalStorageSync().exists()).toBe(true);
expect(findLocalStorageSync().props('asString')).toBe(true);
});
it('passes the right props', () => {
......
......@@ -261,7 +261,10 @@ describe('Web IDE link component', () => {
});
it('should update local storage when selection changes', async () => {
expect(findLocalStorageSync().props('value')).toBe(ACTION_WEB_IDE.key);
expect(findLocalStorageSync().props()).toMatchObject({
asString: true,
value: ACTION_WEB_IDE.key,
});
findActionsButton().vm.$emit('select', ACTION_GITPOD.key);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment