Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion backend/actions/Dashboard/getDashboards.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ module.exports = ({ studioConnection }) => async function getDashboards(params)

await authorize('Dashboard.getDashboards', roles);

const dashboards = await Dashboard.find().sort({ createdAt: -1, _id: -1 }).lean();
const dashboards = await Dashboard
.find()
.sort({ isPinned: -1, createdAt: -1, _id: -1 });

Comment thread
vkarpov15 marked this conversation as resolved.
return { dashboards };
};
25 changes: 19 additions & 6 deletions backend/actions/Dashboard/updateDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@ const UpdateDashboardParams = new Archetype({
$required: true
},
code: {
$type: 'string',
$required: true
$type: 'string'
},
title: {
$type: 'string'
},
description: {
$type: 'string'
},
isPinned: {
$type: 'boolean'
},
Comment thread
vkarpov15 marked this conversation as resolved.
roles: {
$type: ['string']
}
}).compile('UpdateDashboardParams');

module.exports = ({ studioConnection }) => async function updateDashboard(params) {
const { dashboardId, code, title, description, roles, evaluate } = new UpdateDashboardParams(params);
const { dashboardId, code, title, description, isPinned, roles, evaluate } = new UpdateDashboardParams(params);

const Dashboard = studioConnection.models['__Studio_Dashboard'];

await authorize('Dashboard.updateDashboard', roles);

const updateObj = { code };
const updateObj = {};

if (code != null) {
updateObj.code = code;
}

if (title != null) {
updateObj.title = title;
Expand All @@ -40,8 +46,15 @@ module.exports = ({ studioConnection }) => async function updateDashboard(params
updateObj.description = description;
}

const doc = await Dashboard.
findByIdAndUpdate(dashboardId, updateObj, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true });
if (isPinned != null) {
updateObj.isPinned = isPinned;
}

const doc = await Dashboard.findByIdAndUpdate(
dashboardId,
updateObj,
{ sanitizeFilter: true, returnDocument: 'after' }
);

return { doc };
};
11 changes: 9 additions & 2 deletions backend/db/dashboardSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const dashboardSchema = new mongoose.Schema({
description: {
type: String
},
isPinned: {
type: Boolean
},
Comment thread
vkarpov15 marked this conversation as resolved.
createdById: {
type: mongoose.Schema.Types.ObjectId
},
Expand All @@ -30,10 +33,14 @@ dashboardSchema.post(['find', 'findOne'], async function(docs) {
const dashboards = Array.isArray(docs) ? docs : [docs];
for (const dashboard of dashboards) {
if (dashboard != null && dashboard.createdAt == null && dashboard._id?.getTimestamp) {
dashboard.createdAt = dashboard._id.getTimestamp();
const createdAt = dashboard._id.getTimestamp();
if (!createdAt.valueOf()) {
continue;
}
dashboard.set('createdAt', createdAt, { overwriteImmutable: true });
Comment thread
vkarpov15 marked this conversation as resolved.
await this.model.db.model('__Studio_Dashboard').updateOne(
{ _id: dashboard._id },
{ createdAt: dashboard._id.getTimestamp() },
{ createdAt },
{ overwriteImmutable: true }
);
}
Expand Down
1 change: 1 addition & 0 deletions frontend/public/images/pin-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/public/images/pin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions frontend/src/dashboards/dashboards.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ <h3 class="text-sm font-semibold text-content">No matching dashboards</h3>
</dl>
</div>
<div class="flex items-center gap-2 shrink-0">
<button
type="button"
@click="togglePin(dashboard)"
:title="dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'"
:aria-label="dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'"
class="rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page"
Comment thread
Copilot marked this conversation as resolved.
:class="dashboard.isPinned ? 'text-primary ring-primary/40' : ''">
<img :src="dashboard.isPinned ? 'images/pin-off.svg' : 'images/pin.svg'" class="w-4 h-4" alt="" />
</button>
<router-link
:to="'/dashboard/' + dashboard._id"
class="rounded-md bg-primary px-3 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover">
Expand Down
22 changes: 20 additions & 2 deletions frontend/src/dashboards/dashboards.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ module.exports = app => app.component('dashboards', {
computed: {
filteredDashboards() {
const searchWords = getSearchWords(this.searchText);
let dashboards = this.dashboards;
if (searchWords.length === 0) {
return this.dashboards;
return sortPinnedFirst(dashboards);
}
const searchPatterns = searchWords.map(word => new RegExp(escapeRegExp(word), 'i'));

return this.dashboards.filter(dashboard => {
dashboards = dashboards.filter(dashboard => {
const searchableText = `${dashboard.title || ''} ${dashboard.description || ''}`;
return searchPatterns.every(pattern => pattern.test(searchableText));
});

return sortPinnedFirst(dashboards);
}
},
methods: {
Expand Down Expand Up @@ -94,6 +97,17 @@ module.exports = app => app.component('dashboards', {
insertNewDashboard(dashboard) {
this.dashboards.unshift(dashboard);
this.showCreateDashboardModal = false;
},
async togglePin(dashboard) {
if (!dashboard) {
return;
}

const { doc } = await api.Dashboard.updateDashboard({
dashboardId: dashboard._id,
isPinned: !dashboard.isPinned
});
dashboard.isPinned = doc.isPinned;
}
},
directives: {
Expand Down Expand Up @@ -135,3 +149,7 @@ function getSearchWords(searchText) {
return Array.from(new Set(searchText.trim().split(/\s+/).filter(Boolean))).
sort((a, b) => b.length - a.length);
}

function sortPinnedFirst(dashboards) {
return dashboards.slice().sort((a, b) => Number(Boolean(b.isPinned)) - Number(Boolean(a.isPinned)));
}
Loading