Skip to content

Commit 65c93f2

Browse files
fix: keep project version data in telemetry after a clear (#24827)
The dev-mode usage statistics file is shared by all dev servers running on a machine. After a successful upload StatisticsSender clears the whole projects array, which also wipes the data of other still-running dev servers that did not trigger the upload. Until now the version and source identity (flowVersion, vaadinVersion, hillaVersion, sourceId) was only written by trackGlobalData() at dev-mode startup, so any event from a continued session (live reload, browser data, ...) recreated the project entry with just that event's field and no versions. The next report then contained rows with an empty flowVersion and devModeStarts == 0. Re-assert the project identity data whenever the running session writes project data and the entry is missing it, so a continued session repairs its own entry before the next report is sent. devModeStarts stays a true per-interval counter (0 for a continued session) but now always comes with complete version information. --------- Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
1 parent effcd7c commit 65c93f2

3 files changed

Lines changed: 106 additions & 12 deletions

File tree

vaadin-dev-server/src/main/java/com/vaadin/base/devserver/stats/DevModeUsageStatistics.java

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,55 @@ private void trackGlobalData() {
151151
}
152152

153153
// Update basic project statistics and save
154-
projectData.setValue(StatisticsConstants.FIELD_FLOW_VERSION,
155-
Version.getFullVersion());
156-
projectData.setValue(StatisticsConstants.FIELD_VAADIN_VERSION,
157-
ServerInfo.fetchVaadinVersion());
158-
projectData.setValue(StatisticsConstants.FIELD_HILLA_VERSION,
159-
ServerInfo.fetchHillaVersion());
160-
projectData.setValue(StatisticsConstants.FIELD_SOURCE_ID,
161-
ProjectHelpers.getProjectSource(projectFolder));
154+
populateProjectData(projectData);
162155
projectData.increment(
163156
StatisticsConstants.FIELD_PROJECT_DEVMODE_STARTS);
164157
});
165158

166159
}
167160

161+
/**
162+
* Populates the static identity data (versions and source id) of the
163+
* current project.
164+
*
165+
* @param projectData
166+
* the project specific data to populate
167+
*/
168+
private void populateProjectData(StatisticsContainer projectData) {
169+
projectData.setValue(StatisticsConstants.FIELD_FLOW_VERSION,
170+
Version.getFullVersion());
171+
projectData.setValue(StatisticsConstants.FIELD_VAADIN_VERSION,
172+
ServerInfo.fetchVaadinVersion());
173+
projectData.setValue(StatisticsConstants.FIELD_HILLA_VERSION,
174+
ServerInfo.fetchHillaVersion());
175+
projectData.setValue(StatisticsConstants.FIELD_SOURCE_ID,
176+
ProjectHelpers.getProjectSource(projectFolder));
177+
}
178+
179+
/**
180+
* Makes sure the current project entry carries its identity data.
181+
* <p>
182+
* The statistics file is shared by all dev servers running on the machine,
183+
* and after a successful upload the whole projects array is cleared (see
184+
* {@link StatisticsSender}). That clear also wipes the data of other dev
185+
* servers that did not trigger the upload. Any subsequent event from a
186+
* still-running session (live reload, browser data, ...) would otherwise
187+
* recreate the project entry with only that event's field and no version
188+
* information, leading to reports with empty {@code flowVersion} and
189+
* {@code devModeStarts == 0}. Re-asserting the identity data when it is
190+
* missing lets a continued session repair its own entry before the next
191+
* report is sent.
192+
*
193+
* @param projectData
194+
* the project specific data to verify and repair
195+
*/
196+
private void ensureProjectData(StatisticsContainer projectData) {
197+
if (!projectData
198+
.containsField(StatisticsConstants.FIELD_FLOW_VERSION)) {
199+
populateProjectData(projectData);
200+
}
201+
}
202+
168203
/**
169204
* Stores telemetry data received from the browser.
170205
*
@@ -179,6 +214,7 @@ public static void handleBrowserData(JsonNode data) {
179214
}
180215

181216
get().storage.update((global, project) -> {
217+
get().ensureProjectData(project);
182218
try {
183219
String json = data.get("browserData").toString();
184220
JsonNode clientData = JsonHelpers.getJsonMapper()
@@ -218,7 +254,10 @@ public static void collectEvent(String name) {
218254
}
219255

220256
try {
221-
get().storage.update((global, project) -> project.increment(name));
257+
get().storage.update((global, project) -> {
258+
get().ensureProjectData(project);
259+
project.increment(name);
260+
});
222261
} catch (Exception e) {
223262
getLogger().debug("Failed to log '" + name + "'", e);
224263
}
@@ -240,8 +279,10 @@ public static void collectEvent(String name, double value) {
240279
return;
241280

242281
try {
243-
get().storage.update(
244-
(global, project) -> project.aggregate(name, value));
282+
get().storage.update((global, project) -> {
283+
get().ensureProjectData(project);
284+
project.aggregate(name, value);
285+
});
245286
} catch (Exception e) {
246287
getLogger().debug("Failed to collect event '" + name + "'", e);
247288
}
@@ -260,7 +301,10 @@ public void set(String name, String value) {
260301
return;
261302

262303
try {
263-
storage.update((global, project) -> project.setValue(name, value));
304+
storage.update((global, project) -> {
305+
ensureProjectData(project);
306+
project.setValue(name, value);
307+
});
264308
} catch (Exception e) {
265309
getLogger().debug("Failed to set '" + name + "'", e);
266310
}

vaadin-dev-server/src/main/java/com/vaadin/base/devserver/stats/StatisticsContainer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ public void aggregate(String name, double newValue) {
126126
json.put(name, newValue);
127127
}
128128

129+
/**
130+
* Checks whether the given field is present.
131+
*
132+
* @param name
133+
* name of the field to check
134+
* @return {@code true} if the field is present, {@code false} otherwise
135+
*/
136+
public boolean containsField(String name) {
137+
return json.has(name);
138+
}
139+
129140
/**
130141
* Returns the given field value as a string.
131142
*

vaadin-dev-server/src/test/java/com/vaadin/base/devserver/stats/DevModeUsageStatisticsTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.vaadin.flow.internal.JacksonUtils;
2727
import com.vaadin.flow.internal.StringUtil;
28+
import com.vaadin.flow.server.Version;
2829
import com.vaadin.flow.testutil.TestUtils;
2930
import com.vaadin.pro.licensechecker.MachineId;
3031

@@ -211,6 +212,44 @@ void multipleProjects() throws Exception {
211212

212213
}
213214

215+
@Test
216+
void eventAfterClearRestoresProjectVersionData() {
217+
// A running dev session for this project
218+
File mavenProjectFolder = TestUtils
219+
.getTestFolder("stats-data/maven-project-folder1");
220+
DevModeUsageStatistics.init(mavenProjectFolder, storage, sender);
221+
222+
// A successful upload (possibly triggered by another project sharing
223+
// the machine-wide statistics file) clears all project data
224+
storage.clearAllProjectData();
225+
assertEquals(0, getNumberOfProjects(storage.read()),
226+
"Expected the projects array to be cleared");
227+
228+
// The still-running session keeps reporting events without a restart
229+
DevModeUsageStatistics
230+
.collectEvent(StatisticsConstants.EVENT_LIVE_RELOAD);
231+
232+
// The recreated entry must still carry the project identity data so
233+
// that the next report does not contain an empty-version row
234+
StatisticsContainer projectData = new StatisticsContainer(
235+
storage.readProject());
236+
assertEquals(Version.getFullVersion(),
237+
projectData.getValue(StatisticsConstants.FIELD_FLOW_VERSION),
238+
"flowVersion must be restored after a clear");
239+
assertEquals("https://start.vaadin.com/test/1",
240+
projectData.getValue(StatisticsConstants.FIELD_SOURCE_ID),
241+
"sourceId must be restored after a clear");
242+
// No restart happened, so devModeStarts stays 0 for this interval
243+
assertEquals(0,
244+
projectData.getValueAsInt(
245+
StatisticsConstants.FIELD_PROJECT_DEVMODE_STARTS),
246+
"devModeStarts must not be incremented by a plain event");
247+
assertEquals(1,
248+
projectData
249+
.getValueAsInt(StatisticsConstants.EVENT_LIVE_RELOAD),
250+
"The live reload event must be recorded");
251+
}
252+
214253
@Test
215254
void mavenProjectProjectId() {
216255
File mavenProjectFolder1 = TestUtils

0 commit comments

Comments
 (0)