Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show project cache usage stats in widget #89

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
include "com.enonic.xp:lib-content:${xpVersion}"
include "com.enonic.xp:lib-context:${xpVersion}"
include "com.enonic.xp:lib-io:${xpVersion}"
include "com.enonic.xp:lib-node:${xpVersion}"
include "com.enonic.xp:lib-project:${xpVersion}"
include "com.enonic.xp:lib-portal:${xpVersion}"
include "com.enonic.xp:lib-project:${xpVersion}"
Expand Down
23 changes: 23 additions & 0 deletions src/main/resources/admin/widgets/booster/booster.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,29 @@ <h6 id="widget-booster-confirmation-text" class="question">Are you sure?</h6>
</div>
</div>

<table id="widget-booster-commonlycachedpaths">
<caption>Commonly Cached Paths</caption>
<thead>
<tr>
<!-- We put count before path so that when path is too long for the widget width, we still see both columns -->
<th>Count</th>
<th>Path</th>
</tr>
</thead>
<tbody>
{{#commonlyCachedPaths}}
<tr>
<td>{{docCount}}</td>
<td>
<details data-detailurl="{{pathStatsServiceUrl}}?path={{key}}">
<summary>{{key}}</summary>
<div class="detail-target"></div>
</details>
</td>
</tr>
{{/commonlyCachedPaths}}
</tbody>
</table>

{{/isEnabled}}

Expand Down
27 changes: 27 additions & 0 deletions src/main/resources/admin/widgets/booster/booster.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const portal = require('/lib/xp/portal');
const contentLib = require('/lib/xp/content');
const mustache = require('/lib/mustache');
const helper = require("/lib/helper");
const nodeLib = require('/lib/xp/node');

const forceArray = (data) => (Array.isArray(data) ? data : new Array(data));

Expand All @@ -28,6 +29,30 @@ const isAppEnabledOnSite = (contentId) => {
return !!siteConfig;
}

const getCommonlyCachedPaths = (project, numResults) => {
const boosterRepo = nodeLib.connect({
repoId: 'com.enonic.app.booster',
branch: 'master'
});

const allCachedPaths = boosterRepo.query({
start: 0,
count: 0,
query: `project = '${project}'`,
rymsha marked this conversation as resolved.
Show resolved Hide resolved
aggregations: {
paths: {
terms: {
field: 'path',
order: '_count DESC',
size: numResults
}
}
}
});

return allCachedPaths.total ? allCachedPaths.aggregations.paths.buckets : [];
}

const renderWidgetView = (req) => {
let error, hint;
let size;
Expand Down Expand Up @@ -60,6 +85,8 @@ const renderWidgetView = (req) => {
serviceUrl: portal.serviceUrl({ service: 'booster' }),
isLicenseValid: helper.isLicenseValid(),
licenseUploadUrl: portal.serviceUrl({ service: 'license-upload' }),
commonlyCachedPaths: getCommonlyCachedPaths(project, 20),
pathStatsServiceUrl: portal.serviceUrl({ service: 'pathstats' }),
error,
hint
};
Expand Down
21 changes: 21 additions & 0 deletions src/main/resources/assets/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,26 @@
});

confirmNoButton && confirmNoButton.addEventListener('click', () => hideConfirmation());

pathDetails.forEach(detailElement => {
detailElement.addEventListener('click', () => {
const detailTarget = detailElement.querySelector('.detail-target');
// Only load details once
if (detailElement.getAttribute('data-loaded') === 'true') {
return;
}

fetch(detailElement.getAttribute('data-detailurl'))
.then(response => response.text())
.then(html => {
detailTarget.innerHTML = html;
detailElement.setAttribute('data-loaded', 'true');
})
.catch(error => {
console.error('Error fetching details:', error);
});
});
});
}

const serviceUrl = document.currentScript.getAttribute('data-service-url');
Expand All @@ -173,6 +193,7 @@
const confirmYesButton = document.getElementById('widget-booster-confirmation-button-yes');
const confirmNoButton = document.getElementById('widget-booster-confirmation-button-no');
const responseContainer = document.getElementById('widget-booster-action-response');
const pathDetails = document.querySelectorAll('#widget-booster-commonlycachedpaths details');

initEventListeners();

Expand Down
18 changes: 18 additions & 0 deletions src/main/resources/assets/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,22 @@
font-style: italic;
margin-top: 10px;
}

table {
border-collapse: collapse;
text-align: left;
min-width: 100%; /* mitigates aggressive word-wrap on caption and cells */
}
caption {
font-weight: bold;
text-align: left;
}
th, td {
border: 1px solid lightgray;
padding: 4px 7px;
}

details summary {
cursor: pointer;
}
}
17 changes: 17 additions & 0 deletions src/main/resources/services/pathstats/pathstats.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<table>
<caption>Cached querystring params</caption>
<thead>
<tr>
<th>Param</th>
<th>Unique value count</th>
</tr>
</thead>
<tbody>
{{#uniqueParams}}
<tr>
<td>{{paramName}}</td>
<td>{{uniqueValuesCount}}</td>
</tr>
{{/uniqueParams}}
</tbody>
</table>
111 changes: 111 additions & 0 deletions src/main/resources/services/pathstats/pathstats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const mustache = require('/lib/mustache');
const portal = require('/lib/xp/portal');
const nodeLib = require('/lib/xp/node');

function parseQueryString(queryString) {
if (!queryString) {
return null;
}

return queryString.split('&').reduce((acc, pair) => {
const parts = pair.split('=');
const key = parts[0];
const value = parts[1];
rymsha marked this conversation as resolved.
Show resolved Hide resolved

if (!acc[key]) {
acc[key] = {};
}

acc[key][value] = true;
return acc;
}, {});
}

const getUniqueParams = (urlBuckets) => {
const queryStrings = urlBuckets.map(url => url.key ? url.key.split('?')[1] : '');

const parsedQueryStrings = queryStrings.map(parseQueryString).filter((parsed) => {
return parsed !== null;
});

let allParams = {};
parsedQueryStrings.forEach((parsed) => {
for (let param in parsed) {
if (parsed.hasOwnProperty(param)) {
allParams[param] = true;
}
}
});

let result = [];
for (let param in allParams) {
if (allParams.hasOwnProperty(param)) {
let uniqueValues = {};
parsedQueryStrings.forEach((parsed) => {
if (parsed[param]) {
for (let value in parsed[param]) {
if (parsed[param].hasOwnProperty(value)) {
uniqueValues[value] = true;
}
}
}
});
const uniqueValuesArray = Object.keys(uniqueValues);

result.push({
paramName: param,
uniqueValuesCount: uniqueValuesArray.length
});
result.sort((a, b) => {
// Sort descending
return b.uniqueValuesCount - a.uniqueValuesCount;
});
}
}

return result;
}

const renderPathStats = (req) => {
const view = resolve('pathstats.html');
const path = req.params.path;

const boosterRepo = nodeLib.connect({
repoId: 'com.enonic.app.booster',
branch: 'master'
});

const pathUrls = boosterRepo.query({
start: 0,
count: 0,
query: 'path = "' + path + '"',
rymsha marked this conversation as resolved.
Show resolved Hide resolved
aggregations: {
urls: {
terms: {
field: 'url',
size: 10000
}
}
}
});

const uniqueParams = getUniqueParams(pathUrls.aggregations.urls.buckets);

if (uniqueParams.length) {
return {
contentType: 'text/html',
body: mustache.render(view, {
assetsUri: portal.assetUrl({ path: ''}),
path,
uniqueParams
})
}
}

return {
contentType: 'text/html',
body: '<p>(no querystring params in cache)</p>'
};
}

exports.get = (req) => renderPathStats(req);
Loading