Skip to content

Commit

Permalink
Merge pull request #116 from bkarimii/bug/local-storage-version-control
Browse files Browse the repository at this point in the history
#100 - Bug/local storage version control - Fikret Ellek
  • Loading branch information
fikretellek authored Jan 19, 2025
2 parents 0d05002 + a45c5d1 commit 031b470
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 32 deletions.
5 changes: 4 additions & 1 deletion web/src/pages/DisplayTravelResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";

import { getLocalStorage } from "../services/localStorageService";

import { ReportMaker } from "./ReportMaker";
import Visualise from "./Visualise";
import "./DisplayComponent.css";
Expand Down Expand Up @@ -145,7 +147,8 @@ function DisplayTravelResults() {
const fetchTravelData = async (URL) => {
setLoading(true);
try {
const bodyData = localStorage.getItem("newMeetingData v2");

const bodyData = JSON.stringify(getLocalStorage());

const response = await fetch(URL, {
method: "POST",
Expand Down
47 changes: 16 additions & 31 deletions web/src/pages/NewMeeting.jsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { isArray } from "chart.js/helpers";
import PropTypes from "prop-types";
import { useEffect, useState } from "react";
import "./NewMeeting.css";
import { useNavigate } from "react-router-dom";

import {
getLocalStorage,
setLocalStorage,
} from "../services/localStorageService.js";

function NewMeeting() {
const [stations, setStations] = useState([]);
const [helpIconToggle, setHelpIconToggle] = useState(false);
const [formData, setFormData] = useState(() => {
const savedData = JSON.parse(localStorage.getItem("newMeetingData v2"));
if (savedData) {
if (!isArray(savedData.copyOfMeetingStations)) {
savedData.copyOfMeetingStations = [
{ station: savedData.copyOfMeetingStations },
];
}
if (!isArray(savedData.attendees)) {
savedData.attendees = [{ name: "", station: "" }];
}
}
return (
savedData || {
copyOfMeetingStations: [{ station: "" }],
meetingDate: "",
earliestStartTime: "",
latestStartTime: "",
attendees: [{ name: "", station: "" }],
intervalTime: 20,
userTimeZone: "",
}
);
});

const [formData, setFormData] = useState(() => getLocalStorage());

const [copyOfMeetingStations, setCopyOfMeetingStations] = useState(
formData.copyOfMeetingStations || [{ station: { station_name: "" } }],
formData.copyOfMeetingStations,
);
const [meetingDate, setMeetingDate] = useState(formData.meetingDate);
const [earliestStartTime, setEarliestStartTime] = useState(
Expand All @@ -51,6 +33,8 @@ function NewMeeting() {
formData.attendees.map(() => ""),
);

const version = formData.version;

const [filteredAttendeeStations, setFilteredAttendeeStations] = useState(
formData.attendees.map(() => []),
);
Expand Down Expand Up @@ -86,7 +70,9 @@ function NewMeeting() {
latestStartTime,
attendees,
intervalTime,
version,
userTimeZone,

});
};
updateFormData();
Expand All @@ -97,13 +83,12 @@ function NewMeeting() {
latestStartTime,
attendees,
intervalTime,
userTimeZone,
version,
userTimeZone,
]);

useEffect(() => {
document.title = "ThisAppointment";
localStorage.setItem("newMeetingData v2", JSON.stringify(formData));
console.log(formData, "<------Form data");
setLocalStorage(formData);
}, [formData]);

const handleMeetingChange = (field, value) => {
Expand Down Expand Up @@ -328,7 +313,7 @@ function NewMeeting() {
e.preventDefault();
setCopyOfMeetingStations([
...copyOfMeetingStations,
{ station: { station_name: "" } },
{ station: { station_name: "", crs_code: "" } },
]);
}}
aria-label="Add station"
Expand Down
196 changes: 196 additions & 0 deletions web/src/services/localStorageService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
TO UPDATE LOCALSTORAGE STRUCTURE PLEASE FOLLOW THE RULES BELOW
1- follow semantic versioning ---> major.minor.patch ex --> 1.3.1
major updates --> if you change the structure or the keys of the localStorage
minor updates --> if you are adding a new key to structure
patch upgrades --> if the old versions are completely compatiple with new versions (only version variable will be changed)
2- update currentVersion and meetingDataStructure in this file
3- update defaultMeetingStructure from the test file
-- */

const localStorageName = "meetingData";
const currentVersion = "1.0.1";
const meetingDataStructure = {
version: "1.0.1",
copyOfMeetingStations: [{ station: { station_name: "", crs_code: "" } }],
meetingDate: "",
earliestStartTime: "",
latestStartTime: "",
attendees: [
{ name: "", station: "" },
{ name: "", station: "" },
],
intervalTime: 20,
};

export function getLocalStorage() {
const localStorageObject = localStorage.getItem(localStorageName);
let parsedData = {};

if (localStorageObject) {
try {
parsedData = JSON.parse(localStorageObject);
} catch (error) {
console.error("Error parsing localStorage:", error);
return meetingDataStructure;
}
}

const latestVersion = findLatestVersion(parsedData);

if (latestVersion) {
parsedData[currentVersion] = migrateMeetingData(
parsedData[latestVersion],
meetingDataStructure,
);
} else {
parsedData[currentVersion] = meetingDataStructure;

localStorage.setItem(localStorageName, JSON.stringify(parsedData));
console.warn(
"Version mismatch: local storage version is old, initializing storage for current version",
);
}

return parsedData[currentVersion];
}

export function setLocalStorage(newMeetingData) {
if (newMeetingData.version !== currentVersion) {
console.error("Version mismatch: local storage version is not updated");
return;
}

const isSameStructure = compareStructures(
meetingDataStructure,
newMeetingData,
);

if (!isSameStructure) {
console.error(
"Local storage structure is not matching, Please update the version and template structure in meetingDataStructure variable",
);
return;
}

const localStorageObject = localStorage.getItem(localStorageName);
let parsedData = {};

if (localStorageObject) {
try {
parsedData = JSON.parse(localStorageObject);
} catch (error) {
console.warn("Error parsing localStorage:", error);
}
}

parsedData[currentVersion] = newMeetingData;

localStorage.setItem(localStorageName, JSON.stringify(parsedData));
}

function compareStructures(template, data) {
if (
typeof data !== "object" ||
data === null ||
typeof template !== "object"
) {
return typeof data == typeof template;
}
if (template.version && data.version && template.version !== data.version) {
return false;
}

if (Array.isArray(template) && Array.isArray(data)) {
return data.every((item) => compareStructures(template[0], item));
}

const templateKeys = Object.keys(template);
const dataKeys = Object.keys(data);

if (templateKeys.length !== dataKeys.length) return false;

return templateKeys.every((key) => {
if (!Object.prototype.hasOwnProperty.call(data, key)) return false;
return compareStructures(template[key], data[key]);
});
}

function parseVersion(version) {
if (!/^\d+\.\d+\.\d+$/.test(version)) {
return { major: 0, minor: 0, patch: 0 };
}
const [major, minor, patch] = version.split(".").map(Number);
return { major, minor, patch };
}

function compareVersion(version) {
const old = parseVersion(version);
const current = parseVersion(currentVersion);

if (old.major < current.major) {
return "MAJOR";
} else if (old.minor < current.minor) {
return "MINOR";
} else if (old.patch < current.patch) {
return "PATCH";
} else if (JSON.stringify(old) === JSON.stringify(current)) {
return "SAME";
}
return "DEFAULT";
}

export function migrateMeetingData(oldData, newData) {
const versionStatus = compareVersion(oldData.version);

switch (versionStatus) {
case "MAJOR":
return newData;
case "MINOR":
return {
...newData,
...oldData,
version: currentVersion,
};
case "PATCH":
return {
...oldData,
version: currentVersion,
};
case "SAME":
return oldData;
default:
return newData;
}
}

function findLatestVersion(meetingData) {
if (
!meetingData ||
typeof meetingData !== "object" ||
Array.isArray(meetingData) ||
Object.keys(meetingData).length === 0
) {
return null;
}

const latestVersion = Object.keys(meetingData).sort((a, b) => {
const versionA = parseVersion(a);
const versionB = parseVersion(b);
return (
versionB.major - versionA.major ||
versionB.minor - versionA.minor ||
versionB.patch - versionA.patch
);
})[0];

return latestVersion;
}
Loading

0 comments on commit 031b470

Please sign in to comment.