mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Add user custom fields to user directory (#13238)
This commit is contained in:
parent
2334c3622e
commit
0cba4d73c1
@ -5,4 +5,5 @@ export default Component.extend({
|
|||||||
tagName: "tr",
|
tagName: "tr",
|
||||||
classNameBindings: ["me"],
|
classNameBindings: ["me"],
|
||||||
me: propertyEqual("item.user.id", "currentUser.id"),
|
me: propertyEqual("item.user.id", "currentUser.id"),
|
||||||
|
columns: null,
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import Component from "@ember/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNames: ["directory-table-container"],
|
||||||
|
|
||||||
|
@action
|
||||||
|
setActiveHeader(header) {
|
||||||
|
// After render, scroll table left to ensure the order by column is visible
|
||||||
|
const scrollPixels =
|
||||||
|
header.offsetLeft + header.offsetWidth + 10 - this.element.offsetWidth;
|
||||||
|
|
||||||
|
if (scrollPixels > 0) {
|
||||||
|
this.element.scrollLeft = scrollPixels;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,4 @@
|
|||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import I18n from "I18n";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
@ -10,15 +8,8 @@ export default Component.extend({
|
|||||||
labelKey: null,
|
labelKey: null,
|
||||||
chevronIcon: null,
|
chevronIcon: null,
|
||||||
columnIcon: null,
|
columnIcon: null,
|
||||||
|
translated: false,
|
||||||
@discourseComputed("field", "labelKey")
|
onActiveRender: null,
|
||||||
title(field, labelKey) {
|
|
||||||
if (!labelKey) {
|
|
||||||
labelKey = `directory.${this.field}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return I18n.t(labelKey + "_long", { defaultValue: I18n.t(labelKey) });
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleProperties() {
|
toggleProperties() {
|
||||||
if (this.order === this.field) {
|
if (this.order === this.field) {
|
||||||
@ -40,13 +31,12 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
this.set("id", `table-header-toggle-${this.field.replace(/\s/g, "")}`);
|
||||||
this.toggleChevron();
|
this.toggleChevron();
|
||||||
},
|
},
|
||||||
init() {
|
didRender() {
|
||||||
this._super(...arguments);
|
if (this.onActiveRender && this.chevronIcon) {
|
||||||
if (this.icon) {
|
this.onActiveRender(this.element);
|
||||||
let columnIcon = iconHTML(this.icon);
|
|
||||||
this.set("columnIcon", `${columnIcon}`.htmlSafe());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
import Controller from "@ember/controller";
|
||||||
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import EmberObject, { action } from "@ember/object";
|
||||||
|
import { extractError } from "discourse/lib/ajax-error";
|
||||||
|
import { reload } from "discourse/helpers/page-reloader";
|
||||||
|
|
||||||
|
const UP = "up";
|
||||||
|
const DOWN = "down";
|
||||||
|
|
||||||
|
export default Controller.extend(ModalFunctionality, {
|
||||||
|
loading: true,
|
||||||
|
columns: null,
|
||||||
|
labelKey: null,
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
ajax("directory-columns.json")
|
||||||
|
.then((response) => {
|
||||||
|
this.setProperties({
|
||||||
|
loading: false,
|
||||||
|
columns: response.directory_columns
|
||||||
|
.sort((a, b) => (a.position > b.position ? 1 : -1))
|
||||||
|
.map((c) => EmberObject.create(c)),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(extractError);
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
save() {
|
||||||
|
this.set("loading", true);
|
||||||
|
const data = {
|
||||||
|
directory_columns: this.columns.map((c) =>
|
||||||
|
c.getProperties("id", "enabled", "position")
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax("directory-columns.json", { type: "PUT", data })
|
||||||
|
.then(() => {
|
||||||
|
reload();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.set("loading", false);
|
||||||
|
this.flash(extractError(e), "error");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
resetToDefault() {
|
||||||
|
let resetColumns = this.columns;
|
||||||
|
resetColumns
|
||||||
|
.sort((a, b) =>
|
||||||
|
(a.automatic_position || a.user_field.position + 1000) >
|
||||||
|
(b.automatic_position || b.user_field.position + 1000)
|
||||||
|
? 1
|
||||||
|
: -1
|
||||||
|
)
|
||||||
|
.forEach((column, index) => {
|
||||||
|
column.setProperties({
|
||||||
|
position: column.automatic_position || index + 1,
|
||||||
|
enabled: column.automatic,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.set("columns", resetColumns);
|
||||||
|
this.notifyPropertyChange("columns");
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
moveUp(column) {
|
||||||
|
this._moveColumn(UP, column);
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
moveDown(column) {
|
||||||
|
this._moveColumn(DOWN, column);
|
||||||
|
},
|
||||||
|
|
||||||
|
_moveColumn(direction, column) {
|
||||||
|
if (
|
||||||
|
(direction === UP && column.position === 1) ||
|
||||||
|
(direction === DOWN && column.position === this.columns.length)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionOnClick = column.position;
|
||||||
|
const newPosition =
|
||||||
|
direction === UP ? positionOnClick - 1 : positionOnClick + 1;
|
||||||
|
|
||||||
|
const previousColumn = this.columns.find((c) => c.position === newPosition);
|
||||||
|
|
||||||
|
column.set("position", newPosition);
|
||||||
|
previousColumn.set("position", positionOnClick);
|
||||||
|
|
||||||
|
this.set(
|
||||||
|
"columns",
|
||||||
|
this.columns.sort((a, b) => (a.position > b.position ? 1 : -1))
|
||||||
|
);
|
||||||
|
this.notifyPropertyChange("columns");
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
import Controller, { inject as controller } from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
import { equal } from "@ember/object/computed";
|
import { equal } from "@ember/object/computed";
|
||||||
import { longDate } from "discourse/lib/formatter";
|
import { longDate } from "discourse/lib/formatter";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
@ -9,13 +10,14 @@ export default Controller.extend({
|
|||||||
application: controller(),
|
application: controller(),
|
||||||
queryParams: ["period", "order", "asc", "name", "group", "exclude_usernames"],
|
queryParams: ["period", "order", "asc", "name", "group", "exclude_usernames"],
|
||||||
period: "weekly",
|
period: "weekly",
|
||||||
order: "likes_received",
|
order: "",
|
||||||
asc: null,
|
asc: null,
|
||||||
name: "",
|
name: "",
|
||||||
group: null,
|
group: null,
|
||||||
nameInput: null,
|
nameInput: null,
|
||||||
exclude_usernames: null,
|
exclude_usernames: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
columns: null,
|
||||||
|
|
||||||
showTimeRead: equal("period", "all"),
|
showTimeRead: equal("period", "all"),
|
||||||
|
|
||||||
@ -23,9 +25,15 @@ export default Controller.extend({
|
|||||||
this.set("isLoading", true);
|
this.set("isLoading", true);
|
||||||
|
|
||||||
this.set("nameInput", params.name);
|
this.set("nameInput", params.name);
|
||||||
|
this.set("order", params.order);
|
||||||
|
|
||||||
|
const custom_field_columns = this.columns.filter((c) => !c.automatic);
|
||||||
|
const user_field_ids = custom_field_columns
|
||||||
|
.map((c) => c.user_field_id)
|
||||||
|
.join("|");
|
||||||
|
|
||||||
this.store
|
this.store
|
||||||
.find("directoryItem", params)
|
.find("directoryItem", Object.assign(params, { user_field_ids }))
|
||||||
.then((model) => {
|
.then((model) => {
|
||||||
const lastUpdatedAt = model.get("resultSetMeta.last_updated_at");
|
const lastUpdatedAt = model.get("resultSetMeta.last_updated_at");
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
@ -39,6 +47,11 @@ export default Controller.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
showEditColumnsModal() {
|
||||||
|
showModal("edit-user-directory-columns");
|
||||||
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onFilterChanged(filter) {
|
onFilterChanged(filter) {
|
||||||
discourseDebounce(this, this._setName, filter, 500);
|
discourseDebounce(this, this._setName, filter, 500);
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default registerUnbound("mobile-directory-item-label", function (args) {
|
||||||
|
// Args should include key/values { item, column }
|
||||||
|
|
||||||
|
const count = args.item.get(args.column.name);
|
||||||
|
return htmlSafe(I18n.t(`directory.${args.column.name}`, { count }));
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
|
export default registerUnbound(
|
||||||
|
"directory-item-user-field-value",
|
||||||
|
function (args) {
|
||||||
|
// Args should include key/values { item, column }
|
||||||
|
|
||||||
|
const value =
|
||||||
|
args.item.user && args.item.user.user_fields
|
||||||
|
? args.item.user.user_fields[args.column.user_field_id]
|
||||||
|
: null;
|
||||||
|
const content = value || "-";
|
||||||
|
return htmlSafe(`<span class='user-field-value'>${content}</span>`);
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,11 @@
|
|||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
import { number } from "discourse/lib/formatter";
|
||||||
|
|
||||||
|
export default registerUnbound("directory-item-value", function (args) {
|
||||||
|
// Args should include key/values { item, column }
|
||||||
|
|
||||||
|
return htmlSafe(
|
||||||
|
`<span class='number'>${number(args.item.get(args.column.name))}</span>`
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
|
||||||
|
export default registerUnbound("directory-table-header-title", function (args) {
|
||||||
|
// Args should include key/values { field, labelKey, icon, translated }
|
||||||
|
|
||||||
|
let html = "";
|
||||||
|
if (args.icon) {
|
||||||
|
html += iconHTML(args.icon);
|
||||||
|
}
|
||||||
|
let labelKey = args.labelKey || `directory.${args.field}`;
|
||||||
|
|
||||||
|
html += args.translated
|
||||||
|
? args.field
|
||||||
|
: I18n.t(labelKey + "_long", { defaultValue: I18n.t(labelKey) });
|
||||||
|
return htmlSafe(html);
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
queryParams: {
|
queryParams: {
|
||||||
@ -36,11 +37,14 @@ export default DiscourseRoute.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
return params;
|
const columns = PreloadStore.get("directoryColumns");
|
||||||
|
params.order = params.order || columns[0].name;
|
||||||
|
return { params, columns };
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController(controller, params) {
|
setupController(controller, model) {
|
||||||
controller.loadUsers(params);
|
controller.set("columns", model.columns);
|
||||||
|
controller.loadUsers(model.params);
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
<td>{{user-info user=item.user}}</td>
|
<td>{{user-info user=item.user}}</td>
|
||||||
<td>{{number item.likes_received}}</td>
|
{{#each columns as |column|}}
|
||||||
<td>{{number item.likes_given}}</td>
|
<td>
|
||||||
<td>{{number item.topic_count}}</td>
|
{{#if column.automatic}}
|
||||||
<td>{{number item.post_count}}</td>
|
{{directory-item-value item=item column=column}}
|
||||||
<td>{{number item.topics_entered}}</td>
|
{{else}}
|
||||||
<td>{{number item.posts_read}}</td>
|
{{directory-item-user-field-value item=item column=column}}
|
||||||
<td>{{number item.days_visited}}</td>
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
{{#if showTimeRead}}
|
{{#if showTimeRead}}
|
||||||
<td><span class="time-read">{{format-duration item.time_read}}</span></td>
|
<td><span class="time-read">{{format-duration item.time_read}}</span></td>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
{{table-header-toggle field="username" order=order asc=asc}}
|
||||||
|
{{#each columns as |column|}}
|
||||||
|
{{table-header-toggle
|
||||||
|
field=column.name
|
||||||
|
icon=column.icon
|
||||||
|
order=order
|
||||||
|
asc=asc
|
||||||
|
translated=column.user_field_id
|
||||||
|
onActiveRender=setActiveHeader
|
||||||
|
}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if showTimeRead}}
|
||||||
|
<th>{{i18n "directory.time_read"}}</th>
|
||||||
|
{{/if}}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each items as |item|}}
|
||||||
|
{{directory-item item=item columns=columns showTimeRead=showTimeRead}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -1 +1,4 @@
|
|||||||
<span class="header-contents">{{columnIcon}}{{title}}{{chevronIcon}}</span>
|
<span class="header-contents" id={{id}}>
|
||||||
|
{{directory-table-header-title field=field labelKey=labelKey icon=icon translated=translated}}
|
||||||
|
{{chevronIcon}}
|
||||||
|
</span>
|
||||||
|
@ -1,11 +1,33 @@
|
|||||||
{{user-info user=item.user}}
|
{{user-info user=item.user}}
|
||||||
{{user-stat value=item.likes_received label="directory.likes_received" icon="heart"}}
|
|
||||||
{{user-stat value=item.likes_given label="directory.likes_given" icon="heart"}}
|
{{#each columns as |column|}}
|
||||||
{{user-stat value=item.topic_count label="directory.topic_count"}}
|
{{#if column.automatic}}
|
||||||
{{user-stat value=item.post_count label="directory.post_count"}}
|
<div class="user-stat">
|
||||||
{{user-stat value=item.topics_entered label="directory.topics_entered"}}
|
<span class="value">
|
||||||
{{user-stat value=item.posts_read label="directory.posts_read"}}
|
{{directory-item-value item=item column=column}}
|
||||||
{{user-stat value=item.days_visited label="directory.days_visited"}}
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
{{#if column.icon}}
|
||||||
|
{{d-icon column.icon}}
|
||||||
|
{{/if}}
|
||||||
|
{{mobile-directory-item-label item=item column=column}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
{{#if (get item.user.user_fields column.user_field_id)}}
|
||||||
|
<div class="user-stat">
|
||||||
|
<span class="value user-field">
|
||||||
|
{{directory-item-user-field-value item=item column=column}}
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
{{column.name}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
{{#if showTimeRead}}
|
{{#if showTimeRead}}
|
||||||
{{user-stat value=item.time_read label="directory.time_read" type="duration"}}
|
{{user-stat value=item.time_read label="directory.time_read" type="duration"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -17,13 +17,20 @@
|
|||||||
placeholderKey="directory.filter_name"
|
placeholderKey="directory.filter_name"
|
||||||
class="filter-name no-blur"
|
class="filter-name no-blur"
|
||||||
}}
|
}}
|
||||||
|
{{#if currentUser.staff}}
|
||||||
|
{{d-button
|
||||||
|
icon="wrench"
|
||||||
|
action=(action "showEditColumnsModal")
|
||||||
|
class="btn-default open-edit-columns-btn"
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#conditional-loading-spinner condition=model.loading}}
|
{{#conditional-loading-spinner condition=model.loading}}
|
||||||
{{#if model.length}}
|
{{#if model.length}}
|
||||||
<div class="total-rows">{{i18n "directory.total_rows" count=model.totalRows}}</div>
|
<div class="total-rows">{{i18n "directory.total_rows" count=model.totalRows}}</div>
|
||||||
{{#each model as |item|}}
|
{{#each model as |item|}}
|
||||||
{{directory-item tagName="div" class="user" item=item showTimeRead=showTimeRead}}
|
{{directory-item tagName="div" class="user" item=item columns=columns showTimeRead=showTimeRead}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
{{#d-modal-body title="directory.edit_columns.title"}}
|
||||||
|
{{#if loading}}
|
||||||
|
{{loading-spinner size="large"}}
|
||||||
|
{{else}}
|
||||||
|
<div class="edit-directory-columns-container">
|
||||||
|
{{#each columns as |column|}}
|
||||||
|
<div class="edit-directory-column">
|
||||||
|
<div class="left-content">
|
||||||
|
<label class="column-name">
|
||||||
|
{{input type="checkbox" checked=column.enabled}}
|
||||||
|
{{#if column.automatic}}
|
||||||
|
{{directory-table-header-title field=column.name labelKey=labelKey icon=column.icon}}
|
||||||
|
{{else}}
|
||||||
|
{{directory-table-header-title field=column.user_field.name translated=true}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="right-content">
|
||||||
|
{{d-button
|
||||||
|
icon="arrow-up"
|
||||||
|
class="button-secondary move-column-up"
|
||||||
|
action=(action "moveUp" column)
|
||||||
|
}}
|
||||||
|
{{d-button
|
||||||
|
icon="arrow-down"
|
||||||
|
class="button-secondary"
|
||||||
|
action=(action "moveDown" column)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/d-modal-body}}
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{d-button
|
||||||
|
class="btn-primary"
|
||||||
|
label="directory.edit_columns.save"
|
||||||
|
action=(action "save")
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{d-button
|
||||||
|
class="btn-secondary reset-to-default"
|
||||||
|
label="directory.edit_columns.reset_to_default"
|
||||||
|
action=(action "resetToDefault")
|
||||||
|
}}
|
||||||
|
</div>
|
@ -25,33 +25,19 @@
|
|||||||
placeholderKey="directory.filter_name"
|
placeholderKey="directory.filter_name"
|
||||||
class="filter-name no-blur"
|
class="filter-name no-blur"
|
||||||
}}
|
}}
|
||||||
|
{{#if currentUser.staff}}
|
||||||
|
{{d-button
|
||||||
|
icon="wrench"
|
||||||
|
action=(action "showEditColumnsModal")
|
||||||
|
class="btn-default open-edit-columns-btn"
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#conditional-loading-spinner condition=isLoading}}
|
{{#conditional-loading-spinner condition=isLoading}}
|
||||||
{{#if model.length}}
|
{{#if model.length}}
|
||||||
|
{{directory-table items=model columns=columns showTimeRead=showTimeRead order=order asc=asc}}
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
{{table-header-toggle field="username" order=order asc=asc}}
|
|
||||||
{{table-header-toggle field="likes_received" order=order asc=asc icon="heart"}}
|
|
||||||
{{table-header-toggle field="likes_given" order=order asc=asc icon="heart"}}
|
|
||||||
{{table-header-toggle field="topic_count" order=order asc=asc}}
|
|
||||||
{{table-header-toggle field="post_count" order=order asc=asc}}
|
|
||||||
{{table-header-toggle field="topics_entered" order=order asc=asc}}
|
|
||||||
{{table-header-toggle field="posts_read" order=order asc=asc}}
|
|
||||||
{{table-header-toggle field="days_visited" order=order asc=asc}}
|
|
||||||
{{#if showTimeRead}}
|
|
||||||
<th>{{i18n "directory.time_read"}}</th>
|
|
||||||
{{/if}}
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#each model as |item|}}
|
|
||||||
{{directory-item item=item showTimeRead=showTimeRead}}
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
@ -4,6 +4,7 @@ import { visit } from "@ember/test-helpers";
|
|||||||
|
|
||||||
acceptance("User Directory - Mobile", function (needs) {
|
acceptance("User Directory - Mobile", function (needs) {
|
||||||
needs.mobileView();
|
needs.mobileView();
|
||||||
|
|
||||||
test("Visit Page", async function (assert) {
|
test("Visit Page", async function (assert) {
|
||||||
await visit("/u");
|
await visit("/u");
|
||||||
assert.ok(exists(".directory .user"), "has a list of users");
|
assert.ok(exists(".directory .user"), "has a list of users");
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
import {
|
||||||
|
acceptance,
|
||||||
|
exists,
|
||||||
|
query,
|
||||||
|
queryAll,
|
||||||
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { visit } from "@ember/test-helpers";
|
import { click, visit } from "@ember/test-helpers";
|
||||||
|
|
||||||
acceptance("User Directory", function () {
|
acceptance("User Directory", function () {
|
||||||
test("Visit Page", async function (assert) {
|
test("Visit Page", async function (assert) {
|
||||||
@ -25,4 +30,106 @@ acceptance("User Directory", function () {
|
|||||||
assert.ok($("body.users-page").length, "has the body class");
|
assert.ok($("body.users-page").length, "has the body class");
|
||||||
assert.ok(exists(".directory table tr"), "has a list of users");
|
assert.ok(exists(".directory table tr"), "has a list of users");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Custom user fields are present", async function (assert) {
|
||||||
|
await visit("/u");
|
||||||
|
|
||||||
|
const firstRow = query(".users-directory table tr");
|
||||||
|
const columnData = firstRow.querySelectorAll("td");
|
||||||
|
const favoriteColorTd = columnData[columnData.length - 1];
|
||||||
|
|
||||||
|
assert.equal(favoriteColorTd.querySelector("span").textContent, "Blue");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
acceptance("User directory - Editing columns", function (needs) {
|
||||||
|
needs.user({ moderator: true, admin: true });
|
||||||
|
|
||||||
|
test("The automatic columns are checked and the user field columns are unchecked by default", async function (assert) {
|
||||||
|
await visit("/u");
|
||||||
|
await click(".open-edit-columns-btn");
|
||||||
|
|
||||||
|
const columns = queryAll(
|
||||||
|
".edit-directory-columns-container .edit-directory-column"
|
||||||
|
);
|
||||||
|
assert.equal(columns.length, 8);
|
||||||
|
|
||||||
|
const checked = queryAll(
|
||||||
|
".edit-directory-columns-container .edit-directory-column input[type='checkbox']:checked"
|
||||||
|
);
|
||||||
|
assert.equal(checked.length, 7);
|
||||||
|
|
||||||
|
const unchecked = queryAll(
|
||||||
|
".edit-directory-columns-container .edit-directory-column input[type='checkbox']:not(:checked)"
|
||||||
|
);
|
||||||
|
assert.equal(unchecked.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchColumns = function () {
|
||||||
|
return queryAll(".edit-directory-columns-container .edit-directory-column");
|
||||||
|
};
|
||||||
|
|
||||||
|
test("Reordering and restoring default positions", async function (assert) {
|
||||||
|
await visit("/u");
|
||||||
|
await click(".open-edit-columns-btn");
|
||||||
|
|
||||||
|
let columns;
|
||||||
|
columns = fetchColumns();
|
||||||
|
assert.equal(
|
||||||
|
columns[3].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Replies Posted"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
columns[4].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Topics Viewed"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Click on row 4 and see if they are swapped
|
||||||
|
await click(columns[4].querySelector(".move-column-up"));
|
||||||
|
|
||||||
|
columns = fetchColumns();
|
||||||
|
assert.equal(
|
||||||
|
columns[3].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Topics Viewed"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
columns[4].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Replies Posted"
|
||||||
|
);
|
||||||
|
|
||||||
|
const moveUserFieldColumnUpBtn = columns[columns.length - 1].querySelector(
|
||||||
|
".move-column-up"
|
||||||
|
);
|
||||||
|
await click(moveUserFieldColumnUpBtn);
|
||||||
|
await click(moveUserFieldColumnUpBtn);
|
||||||
|
await click(moveUserFieldColumnUpBtn);
|
||||||
|
|
||||||
|
columns = fetchColumns();
|
||||||
|
assert.equal(
|
||||||
|
columns[4].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Favorite Color"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
columns[5].querySelector(".column-name").textContent.trim(),
|
||||||
|
"Replies Posted"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now click restore default and check order of column names
|
||||||
|
await click(".reset-to-default");
|
||||||
|
|
||||||
|
let columnNames = queryAll(
|
||||||
|
".edit-directory-columns-container .edit-directory-column .column-name"
|
||||||
|
).toArray();
|
||||||
|
columnNames = columnNames.map((el) => el.textContent.trim());
|
||||||
|
assert.deepEqual(columnNames, [
|
||||||
|
"Received",
|
||||||
|
"Given",
|
||||||
|
"Topics Created",
|
||||||
|
"Replies Posted",
|
||||||
|
"Topics Viewed",
|
||||||
|
"Posts Read",
|
||||||
|
"Days Visited",
|
||||||
|
"Favorite Color",
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,12 @@ export default {
|
|||||||
likes_given: 7725,
|
likes_given: 7725,
|
||||||
topics_entered: 11453,
|
topics_entered: 11453,
|
||||||
topic_count: 184,
|
topic_count: 184,
|
||||||
post_count: 12263
|
post_count: 12263,
|
||||||
|
user: {
|
||||||
|
user_fields: {
|
||||||
|
3: "Blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -929,4 +929,102 @@ export function applyDefaultHandlers(pretender) {
|
|||||||
|
|
||||||
return [404, { "Content-Type": "application/html" }, ""];
|
return [404, { "Content-Type": "application/html" }, ""];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pretender.get("directory-columns.json", () => {
|
||||||
|
return response(200, {
|
||||||
|
directory_columns: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "likes_received",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 1,
|
||||||
|
position: 1,
|
||||||
|
icon: "heart",
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "likes_given",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 2,
|
||||||
|
position: 2,
|
||||||
|
icon: "heart",
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "topic_count",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 3,
|
||||||
|
position: 3,
|
||||||
|
icon: null,
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "post_count",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 4,
|
||||||
|
position: 4,
|
||||||
|
icon: null,
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "topics_entered",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 5,
|
||||||
|
position: 5,
|
||||||
|
icon: null,
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "posts_read",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 6,
|
||||||
|
position: 6,
|
||||||
|
icon: null,
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "days_visited",
|
||||||
|
automatic: true,
|
||||||
|
enabled: true,
|
||||||
|
automatic_position: 7,
|
||||||
|
position: 7,
|
||||||
|
icon: null,
|
||||||
|
user_field: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: null,
|
||||||
|
automatic: false,
|
||||||
|
enabled: false,
|
||||||
|
automatic_position: null,
|
||||||
|
position: 8,
|
||||||
|
icon: null,
|
||||||
|
user_field: {
|
||||||
|
id: 3,
|
||||||
|
name: "Favorite Color",
|
||||||
|
description: "User's favorite color",
|
||||||
|
field_type: "text",
|
||||||
|
editable: false,
|
||||||
|
required: false,
|
||||||
|
show_on_profile: false,
|
||||||
|
show_on_user_card: true,
|
||||||
|
searchable: true,
|
||||||
|
position: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,12 @@ function setupTestsCommon(application, container, config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
PreloadStore.reset();
|
PreloadStore.reset();
|
||||||
|
PreloadStore.store(
|
||||||
|
"directoryColumns",
|
||||||
|
JSON.parse(
|
||||||
|
'[{"name":"likes_given","automatic":true,"icon":"heart","user_field_id":null},{"name":"posts_read","automatic":true,"icon":null,"user_field_id":null},{"name":"likes_received","automatic":true,"icon":"heart","user_field_id":null},{"name":"topic_count","automatic":true,"icon":null,"user_field_id":null},{"name":"post_count","automatic":true,"icon":null,"user_field_id":null},{"name":"topics_entered","automatic":true,"icon":null,"user_field_id":null},{"name":"days_visited","automatic":true,"icon":null,"user_field_id":null},{"name":"Favorite Color","automatic":false,"icon":null,"user_field_id":3}]'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
sinon.stub(ScrollingDOMMethods, "screenNotFull");
|
sinon.stub(ScrollingDOMMethods, "screenNotFull");
|
||||||
sinon.stub(ScrollingDOMMethods, "bindOnScroll");
|
sinon.stub(ScrollingDOMMethods, "bindOnScroll");
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
.directory {
|
.directory {
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
|
|
||||||
|
.directory-table-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-edit-columns-btn {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0.45em 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
&.users-directory {
|
&.users-directory {
|
||||||
.period-chooser {
|
.period-chooser {
|
||||||
.selected-name {
|
.selected-name {
|
||||||
@ -61,6 +71,13 @@
|
|||||||
.time-read {
|
.time-read {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.user-field-value {
|
||||||
|
font-size: var(--font-up-1);
|
||||||
|
color: var(--primary-medium);
|
||||||
|
@media screen and (max-width: $small-width) {
|
||||||
|
font-size: $font-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th.sortable {
|
th.sortable {
|
||||||
@ -82,3 +99,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-user-directory-columns-modal {
|
||||||
|
.edit-directory-columns-container {
|
||||||
|
.edit-directory-column {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
|
||||||
|
.column-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.d-icon-heart {
|
||||||
|
color: var(--love);
|
||||||
|
margin: 0 0.25em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-column-up {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-content,
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.reset-to-default {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-user-directory-columns-modal .modal-inner-container {
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
font-size: $font-up-1;
|
font-size: $font-up-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.open-edit-columns-btn {
|
||||||
|
margin: -0.7em 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
&.users-directory {
|
&.users-directory {
|
||||||
.filter-name {
|
.filter-name {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -38,6 +42,9 @@
|
|||||||
flex: 1 1 50%;
|
flex: 1 1 50%;
|
||||||
.value {
|
.value {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
&.user-field {
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
margin-left: 0.2em;
|
margin-left: 0.2em;
|
||||||
@ -49,3 +56,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-user-directory-columns-modal .modal-inner-container {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
@ -35,6 +35,9 @@ class Admin::UserFieldsController < Admin::AdminController
|
|||||||
update_options(field)
|
update_options(field)
|
||||||
|
|
||||||
if field.save
|
if field.save
|
||||||
|
if !field.show_on_profile && !field.show_on_user_card
|
||||||
|
DirectoryColumn.where(user_field_id: field.id).destroy_all
|
||||||
|
end
|
||||||
render_serialized(field, UserFieldSerializer, root: 'user_field')
|
render_serialized(field, UserFieldSerializer, root: 'user_field')
|
||||||
else
|
else
|
||||||
render_json_error(field)
|
render_json_error(field)
|
||||||
|
@ -603,6 +603,7 @@ class ApplicationController < ActionController::Base
|
|||||||
store_preloaded("customEmoji", custom_emoji)
|
store_preloaded("customEmoji", custom_emoji)
|
||||||
store_preloaded("isReadOnly", @readonly_mode.to_s)
|
store_preloaded("isReadOnly", @readonly_mode.to_s)
|
||||||
store_preloaded("activatedThemes", activated_themes_json)
|
store_preloaded("activatedThemes", activated_themes_json)
|
||||||
|
store_preloaded("directoryColumns", directory_columns_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def preload_current_user_data
|
def preload_current_user_data
|
||||||
@ -614,6 +615,20 @@ class ApplicationController < ActionController::Base
|
|||||||
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def directory_columns_json
|
||||||
|
DirectoryColumn
|
||||||
|
.left_joins(:user_field)
|
||||||
|
.where(enabled: true)
|
||||||
|
.order(:position)
|
||||||
|
.pluck('directory_columns.name',
|
||||||
|
'directory_columns.automatic',
|
||||||
|
'directory_columns.icon',
|
||||||
|
'user_fields.id',
|
||||||
|
'user_fields.name')
|
||||||
|
.map { |column| { name: column[0] || column[4], automatic: column[1], icon: column[2], user_field_id: column[3] } }
|
||||||
|
.to_json
|
||||||
|
end
|
||||||
|
|
||||||
def custom_html_json
|
def custom_html_json
|
||||||
target = view_context.mobile_view? ? :mobile : :desktop
|
target = view_context.mobile_view? ? :mobile : :desktop
|
||||||
|
|
||||||
|
62
app/controllers/directory_columns_controller.rb
Normal file
62
app/controllers/directory_columns_controller.rb
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DirectoryColumnsController < ApplicationController
|
||||||
|
requires_login
|
||||||
|
|
||||||
|
def index
|
||||||
|
raise Discourse::NotFound unless guardian.is_staff?
|
||||||
|
|
||||||
|
ensure_user_fields_have_columns
|
||||||
|
|
||||||
|
columns = DirectoryColumn.includes(:user_field).all
|
||||||
|
render_json_dump(directory_columns: serialize_data(columns, DirectoryColumnSerializer))
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
raise Discourse::NotFound unless guardian.is_staff?
|
||||||
|
params.require(:directory_columns)
|
||||||
|
directory_column_params = params.permit(directory_columns: {})
|
||||||
|
directory_columns = DirectoryColumn.all
|
||||||
|
|
||||||
|
has_enabled_column = directory_column_params[:directory_columns].values.any? do |column_data|
|
||||||
|
column_data[:enabled].to_s == "true"
|
||||||
|
end
|
||||||
|
raise Discourse::InvalidParameters, "Must have at least one column enabled" unless has_enabled_column
|
||||||
|
|
||||||
|
directory_column_params[:directory_columns].values.each do |column_data|
|
||||||
|
existing_column = directory_columns.detect { |c| c.id == column_data[:id].to_i }
|
||||||
|
if (existing_column.enabled != column_data[:enabled] || existing_column.position != column_data[:position].to_i)
|
||||||
|
existing_column.update(enabled: column_data[:enabled], position: column_data[:position])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_user_fields_have_columns
|
||||||
|
user_fields_without_column =
|
||||||
|
UserField.left_outer_joins(:directory_column)
|
||||||
|
.where(directory_column: { user_field_id: nil })
|
||||||
|
.where("show_on_profile=? OR show_on_user_card=?", true, true)
|
||||||
|
|
||||||
|
return unless user_fields_without_column.count > 0
|
||||||
|
|
||||||
|
next_position = DirectoryColumn.maximum("position") + 1
|
||||||
|
|
||||||
|
new_directory_column_attrs = []
|
||||||
|
user_fields_without_column.each do |user_field|
|
||||||
|
new_directory_column_attrs.push({
|
||||||
|
user_field_id: user_field.id,
|
||||||
|
enabled: false,
|
||||||
|
automatic: false,
|
||||||
|
position: next_position
|
||||||
|
})
|
||||||
|
|
||||||
|
next_position += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
DirectoryColumn.insert_all(new_directory_column_attrs)
|
||||||
|
end
|
||||||
|
end
|
@ -32,6 +32,14 @@ class DirectoryItemsController < ApplicationController
|
|||||||
result = result.order("directory_items.#{order} #{dir}, directory_items.id")
|
result = result.order("directory_items.#{order} #{dir}, directory_items.id")
|
||||||
elsif params[:order] === 'username'
|
elsif params[:order] === 'username'
|
||||||
result = result.order("users.#{order} #{dir}, directory_items.id")
|
result = result.order("users.#{order} #{dir}, directory_items.id")
|
||||||
|
else
|
||||||
|
user_field = UserField.find_by(name: params[:order])
|
||||||
|
if user_field
|
||||||
|
result = result
|
||||||
|
.joins(:user)
|
||||||
|
.joins("LEFT OUTER JOIN user_custom_fields ON user_custom_fields.user_id = users.id AND user_custom_fields.name = 'user_field_#{user_field.id}'")
|
||||||
|
.order("user_custom_fields.name = 'user_field_#{user_field.id}' ASC, user_custom_fields.value #{dir}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if period_type == DirectoryItem.period_types[:all]
|
if period_type == DirectoryItem.period_types[:all]
|
||||||
@ -84,7 +92,14 @@ class DirectoryItemsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
last_updated_at = DirectoryItem.last_updated_at(period_type)
|
last_updated_at = DirectoryItem.last_updated_at(period_type)
|
||||||
render_json_dump(directory_items: serialize_data(result, DirectoryItemSerializer),
|
|
||||||
|
serializer_opts = {}
|
||||||
|
if params[:user_field_ids]
|
||||||
|
serializer_opts[:user_field_ids] = params[:user_field_ids]&.split("|")&.map(&:to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
serialized = serialize_data(result, DirectoryItemSerializer, serializer_opts)
|
||||||
|
render_json_dump(directory_items: serialized,
|
||||||
meta: {
|
meta: {
|
||||||
last_updated_at: last_updated_at,
|
last_updated_at: last_updated_at,
|
||||||
total_rows_directory_items: result_count,
|
total_rows_directory_items: result_count,
|
||||||
|
5
app/models/directory_column.rb
Normal file
5
app/models/directory_column.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DirectoryColumn < ActiveRecord::Base
|
||||||
|
belongs_to :user_field
|
||||||
|
end
|
@ -7,6 +7,7 @@ class UserField < ActiveRecord::Base
|
|||||||
validates_presence_of :description, :field_type
|
validates_presence_of :description, :field_type
|
||||||
validates_presence_of :name, unless: -> { field_type == "confirm" }
|
validates_presence_of :name, unless: -> { field_type == "confirm" }
|
||||||
has_many :user_field_options, dependent: :destroy
|
has_many :user_field_options, dependent: :destroy
|
||||||
|
has_one :directory_column, dependent: :destroy
|
||||||
accepts_nested_attributes_for :user_field_options
|
accepts_nested_attributes_for :user_field_options
|
||||||
|
|
||||||
after_save :queue_index_search
|
after_save :queue_index_search
|
||||||
|
13
app/serializers/directory_column_serializer.rb
Normal file
13
app/serializers/directory_column_serializer.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DirectoryColumnSerializer < ApplicationSerializer
|
||||||
|
attributes :id,
|
||||||
|
:name,
|
||||||
|
:automatic,
|
||||||
|
:enabled,
|
||||||
|
:automatic_position,
|
||||||
|
:position,
|
||||||
|
:icon
|
||||||
|
|
||||||
|
has_one :user_field, serializer: UserFieldSerializer, embed: :objects
|
||||||
|
end
|
@ -4,6 +4,16 @@ class DirectoryItemSerializer < ApplicationSerializer
|
|||||||
|
|
||||||
class UserSerializer < UserNameSerializer
|
class UserSerializer < UserNameSerializer
|
||||||
include UserPrimaryGroupMixin
|
include UserPrimaryGroupMixin
|
||||||
|
|
||||||
|
attributes :user_fields
|
||||||
|
|
||||||
|
def user_fields
|
||||||
|
object.user_fields(@options[:user_field_ids])
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_user_fields?
|
||||||
|
user_fields.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes :id,
|
attributes :id,
|
||||||
@ -23,5 +33,4 @@ class DirectoryItemSerializer < ApplicationSerializer
|
|||||||
def include_time_read?
|
def include_time_read?
|
||||||
object.period_type == DirectoryItem.period_types[:all]
|
object.period_type == DirectoryItem.period_types[:all]
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -646,6 +646,10 @@ en:
|
|||||||
total_rows:
|
total_rows:
|
||||||
one: "%{count} user"
|
one: "%{count} user"
|
||||||
other: "%{count} users"
|
other: "%{count} users"
|
||||||
|
edit_columns:
|
||||||
|
title: "Edit Directory Columns"
|
||||||
|
save: "Save"
|
||||||
|
reset_to_default: "Reset to default"
|
||||||
|
|
||||||
group_histories:
|
group_histories:
|
||||||
actions:
|
actions:
|
||||||
|
@ -387,6 +387,8 @@ Discourse::Application.routes.draw do
|
|||||||
get ".well-known/change-password", to: redirect(relative_url_root + 'my/preferences/account', status: 302)
|
get ".well-known/change-password", to: redirect(relative_url_root + 'my/preferences/account', status: 302)
|
||||||
|
|
||||||
get "user-cards" => "users#cards", format: :json
|
get "user-cards" => "users#cards", format: :json
|
||||||
|
get "directory-columns" => "directory_columns#index", format: :json
|
||||||
|
put "directory-columns" => "directory_columns#update", format: :json
|
||||||
|
|
||||||
%w{users u}.each_with_index do |root_path, index|
|
%w{users u}.each_with_index do |root_path, index|
|
||||||
get "#{root_path}" => "users#index", constraints: { format: 'html' }
|
get "#{root_path}" => "users#index", constraints: { format: 'html' }
|
||||||
|
41
db/migrate/20210527131318_create_directory_columns.rb
Normal file
41
db/migrate/20210527131318_create_directory_columns.rb
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
class CreateDirectoryColumns < ActiveRecord::Migration[6.1]
|
||||||
|
def up
|
||||||
|
create_table :directory_columns do |t|
|
||||||
|
t.string :name, null: true
|
||||||
|
t.integer :automatic_position, null: true
|
||||||
|
t.string :icon, null: true
|
||||||
|
t.integer :user_field_id, null: true
|
||||||
|
t.boolean :automatic, null: false
|
||||||
|
t.boolean :enabled, null: false
|
||||||
|
t.integer :position, null: false
|
||||||
|
t.datetime :created_at, default: -> { 'CURRENT_TIMESTAMP' }
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :directory_columns, [:enabled, :position, :user_field_id], name: "directory_column_index"
|
||||||
|
|
||||||
|
create_automatic_columns
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :directory_columns
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_automatic_columns
|
||||||
|
DB.exec(
|
||||||
|
<<~SQL
|
||||||
|
INSERT INTO directory_columns (
|
||||||
|
name, automatic, enabled, automatic_position, position, icon
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
( 'likes_received', true, true, 1, 1, 'heart' ),
|
||||||
|
( 'likes_given', true, true, 2, 2, 'heart' ),
|
||||||
|
( 'topic_count', true, true, 3, 3, NULL ),
|
||||||
|
( 'post_count', true, true, 4, 4, NULL ),
|
||||||
|
( 'topics_entered', true, true, 5, 5, NULL ),
|
||||||
|
( 'posts_read', true, true, 6, 6, NULL ),
|
||||||
|
( 'days_visited', true, true, 7, 7, NULL );
|
||||||
|
SQL
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
@ -124,6 +124,21 @@ describe Admin::UserFieldsController do
|
|||||||
user_field.reload
|
user_field.reload
|
||||||
expect(user_field.user_field_options.size).to eq(2)
|
expect(user_field.user_field_options.size).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "removes directory column record if not public" do
|
||||||
|
next_position = DirectoryColumn.maximum("position") + 1
|
||||||
|
DirectoryColumn.create(
|
||||||
|
user_field_id: user_field.id,
|
||||||
|
enabled: false,
|
||||||
|
automatic: false,
|
||||||
|
position: next_position
|
||||||
|
)
|
||||||
|
expect {
|
||||||
|
put "/admin/customize/user_fields/#{user_field.id}.json", params: {
|
||||||
|
user_field: { show_on_profile: false, show_on_user_card: false, searchable: true }
|
||||||
|
}
|
||||||
|
}.to change { DirectoryColumn.count }.by(-1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
74
spec/requests/directory_columns_controller_spec.rb
Normal file
74
spec/requests/directory_columns_controller_spec.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe DirectoryColumnsController do
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
fab!(:public_user_field) { Fabricate(:user_field, show_on_profile: true) }
|
||||||
|
fab!(:private_user_field) { Fabricate(:user_field, show_on_profile: false, show_on_user_card: false) }
|
||||||
|
|
||||||
|
it "creates directory column records for public user fields" do
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
get "/directory-columns.json"
|
||||||
|
}.to change { DirectoryColumn.count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 403 when not logged in as staff member" do
|
||||||
|
sign_in(user)
|
||||||
|
get "/directory-columns.json"
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#update" do
|
||||||
|
let(:first_directory_column_id) { DirectoryColumn.first.id }
|
||||||
|
let(:second_directory_column_id) { DirectoryColumn.second.id }
|
||||||
|
let(:params) {
|
||||||
|
{
|
||||||
|
directory_columns: {
|
||||||
|
"0": {
|
||||||
|
id: first_directory_column_id,
|
||||||
|
enabled: false,
|
||||||
|
position: 1
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
id: second_directory_column_id,
|
||||||
|
enabled: true,
|
||||||
|
position: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it "updates exising directory columns" do
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
put "/directory-columns.json", params: params
|
||||||
|
}.to change { DirectoryColumn.find(first_directory_column_id).enabled }.from(true).to(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not let all columns be disabled" do
|
||||||
|
sign_in(admin)
|
||||||
|
bad_params = params
|
||||||
|
bad_params[:directory_columns][:"1"][:enabled] = false
|
||||||
|
|
||||||
|
put "/directory-columns.json", params: bad_params
|
||||||
|
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 when not logged in as a staff member" do
|
||||||
|
sign_in(user)
|
||||||
|
put "/directory-columns.json", params: params
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user