diff --git a/app/assets/javascripts/discourse/app/components/directory-item-user-field-value.gjs b/app/assets/javascripts/discourse/app/components/directory-item-user-field-value.gjs
new file mode 100644
index 00000000000..ede1b8667bd
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/directory-item-user-field-value.gjs
@@ -0,0 +1,57 @@
+import Component from "@glimmer/component";
+import { fn, hash } from "@ember/helper";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { LinkTo } from "@ember/routing";
+import { inject as service } from "@ember/service";
+
+export default class DirectoryItemUserFieldValueComponent extends Component {
+ @service router;
+ get fieldData() {
+ const { item, column } = this.args;
+ return item?.user?.user_fields?.[column.user_field_id];
+ }
+
+ get values() {
+ const fieldData = this.fieldData;
+ if (!fieldData || !fieldData.value) {
+ return null;
+ }
+
+ return fieldData.value
+ .toString()
+ .split(",")
+ .map((v) => v.replace(/-/g, " "))
+ .map((v) => v.trim());
+ }
+
+ get isSearchable() {
+ return this.fieldData?.searchable;
+ }
+
+ @action
+ refreshRoute(value) {
+ this.router.transitionTo({ queryParams: { name: value } });
+ }
+
+
+
+ {{#if this.values}}
+ {{#if this.isSearchable}}
+ {{#each this.values as |value|}}
+ {{value}}
+ {{/each}}
+ {{else}}
+ {{this.values}}
+ {{/if}}
+ {{else}}
+ -
+ {{/if}}
+
+
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-card-contents.hbs b/app/assets/javascripts/discourse/app/components/user-card-contents.hbs
index 8ac6fa2ae50..d51a9c01b31 100644
--- a/app/assets/javascripts/discourse/app/components/user-card-contents.hbs
+++ b/app/assets/javascripts/discourse/app/components/user-card-contents.hbs
@@ -370,7 +370,17 @@
{{#each uf.value as |v|}}
{{! some values are arrays }}
- {{v}}
+
+ {{#if uf.field.searchable}}
+ {{v}}
+ {{else}}
+ {{v}}
+ {{/if}}
+
{{else}}
{{uf.value}}
{{/each}}
diff --git a/app/assets/javascripts/discourse/app/components/user-card-contents.js b/app/assets/javascripts/discourse/app/components/user-card-contents.js
index f529cdf8ba3..80c9fa78e9a 100644
--- a/app/assets/javascripts/discourse/app/components/user-card-contents.js
+++ b/app/assets/javascripts/discourse/app/components/user-card-contents.js
@@ -248,6 +248,11 @@ export default class UserCardContents extends CardContentsBase.extend(
this._close();
}
+ @action
+ refreshRoute(value) {
+ this.router.transitionTo({ queryParams: { name: value } });
+ }
+
@action
handleShowUser(event) {
if (wantsNewWindow(event)) {
diff --git a/app/assets/javascripts/discourse/app/helpers/directory-item-user-field-value.js b/app/assets/javascripts/discourse/app/helpers/directory-item-user-field-value.js
deleted file mode 100644
index 9ea845a159f..00000000000
--- a/app/assets/javascripts/discourse/app/helpers/directory-item-user-field-value.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { htmlSafe } from "@ember/template";
-
-export default function directoryItemUserFieldValue(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(
- `${content}`
- );
-}
diff --git a/app/assets/javascripts/discourse/app/templates/user.hbs b/app/assets/javascripts/discourse/app/templates/user.hbs
index 927c44ae635..632bc8de7ad 100644
--- a/app/assets/javascripts/discourse/app/templates/user.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user.hbs
@@ -260,7 +260,16 @@
{{#each uf.value as |v|}}
{{! some values are arrays }}
- {{v}}
+
+ {{#if uf.field.searchable}}
+ {{v}}
+ {{else}}
+ {{v}}
+ {{/if}}
+
{{else}}
{{uf.value}}
{{/each}}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/users-test.js b/app/assets/javascripts/discourse/tests/acceptance/users-test.js
index 10095882c8b..47543737508 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/users-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/users-test.js
@@ -57,6 +57,34 @@ acceptance("User Directory", function () {
);
});
+ test("Searchable user fields display as links", async function (assert) {
+ pretender.get("/directory_items", () => {
+ return response(cloneJSON(directoryFixtures["directory_items"]));
+ });
+
+ await visit("/u");
+
+ const firstRowUserField = query(
+ ".directory .directory-table__body .directory-table__row:first-child .directory-table__value--user-field"
+ );
+
+ const userFieldLink = firstRowUserField.querySelector("a");
+
+ assert.ok(userFieldLink, "User field is displayed as a link");
+
+ assert.strictEqual(
+ userFieldLink.getAttribute("href"),
+ "/u?name=Blue&order=likes_received",
+ "The link points to the correct URL"
+ );
+
+ assert.strictEqual(
+ userFieldLink.textContent.trim(),
+ "Blue",
+ "Link text is correct"
+ );
+ });
+
test("Visit With Group Filter", async function (assert) {
await visit("/u?group=trust_level_0");
assert.ok(
@@ -75,7 +103,7 @@ acceptance("User Directory", function () {
".directory .directory-table__body .directory-table__row:first-child .directory-table__value--user-field"
);
- assert.strictEqual(firstRowUserField.textContent, "Blue");
+ assert.strictEqual(firstRowUserField.textContent.trim(), "Blue");
});
test("Can sort table via keyboard", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js
index e7309ef2030..2c79cc41417 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js
@@ -14,7 +14,10 @@ export default {
post_count: 12263,
user: {
user_fields: {
- 3: "Blue",
+ 3: {
+ value: ["Blue"],
+ searchable: true
+ },
},
},
},
diff --git a/app/assets/stylesheets/common/base/directory.scss b/app/assets/stylesheets/common/base/directory.scss
index a5fe058a65e..85f7b052ad6 100644
--- a/app/assets/stylesheets/common/base/directory.scss
+++ b/app/assets/stylesheets/common/base/directory.scss
@@ -4,6 +4,11 @@
}
.directory {
+ .directory-value-list-item:not(:empty)
+ ~ .directory-value-list-item:not(:empty):before {
+ content: "| ";
+ }
+
margin-bottom: 100px;
background: var(--d-content-background);
diff --git a/app/controllers/directory_items_controller.rb b/app/controllers/directory_items_controller.rb
index fb1531c19e8..31f797fdb46 100644
--- a/app/controllers/directory_items_controller.rb
+++ b/app/controllers/directory_items_controller.rb
@@ -123,6 +123,9 @@ class DirectoryItemsController < ApplicationController
end
serializer_opts[:attributes] = active_directory_column_names
+ serializer_opts[:searchable_fields] = UserField.where(searchable: true) if serializer_opts[
+ :user_custom_field_map
+ ].present?
serialized = serialize_data(result, DirectoryItemSerializer, serializer_opts)
render_json_dump(
diff --git a/app/serializers/directory_item_serializer.rb b/app/serializers/directory_item_serializer.rb
index bd218f982bf..0094028dfe7 100644
--- a/app/serializers/directory_item_serializer.rb
+++ b/app/serializers/directory_item_serializer.rb
@@ -8,10 +8,25 @@ class DirectoryItemSerializer < ApplicationSerializer
def user_fields
fields = {}
+ user_custom_field_map = @options[:user_custom_field_map] || {}
+ searchable_fields = @options[:searchable_fields] || []
- object.user_custom_fields.each do |cuf|
- user_field_id = @options[:user_custom_field_map][cuf.name]
- fields[user_field_id] = cuf.value if user_field_id
+ object.user_custom_fields.each do |custom_field|
+ user_field_id = user_custom_field_map[custom_field.name]
+ next unless user_field_id
+
+ current_value = fields.dig(user_field_id, :value)
+
+ current_value = Array(current_value) if current_value
+
+ new_value = current_value ? current_value << custom_field.value : custom_field.value
+
+ is_searchable = searchable_fields.any? { |field| field.id == user_field_id }
+
+ fields[user_field_id] = {
+ value: new_value.is_a?(Array) ? new_value : [new_value],
+ searchable: is_searchable,
+ }
end
fields
diff --git a/spec/requests/directory_items_controller_spec.rb b/spec/requests/directory_items_controller_spec.rb
index 361c31570c6..425edffdd39 100644
--- a/spec/requests/directory_items_controller_spec.rb
+++ b/spec/requests/directory_items_controller_spec.rb
@@ -248,7 +248,9 @@ RSpec.describe DirectoryItemsController do
user_fields.each do |data|
user = items[data[:order]]["user"]
expect(user["username"]).to eq(data[:user].username)
- expect(user["user_fields"]).to eq({ data[:field].id.to_s => data[:value] })
+ expect(user["user_fields"]).to eq(
+ { data[:field].id.to_s => { "searchable" => true, "value" => [data[:value]] } },
+ )
end
end
diff --git a/spec/serializers/directory_item_serializer_spec.rb b/spec/serializers/directory_item_serializer_spec.rb
index d6cda074e23..f0b1bd2f748 100644
--- a/spec/serializers/directory_item_serializer_spec.rb
+++ b/spec/serializers/directory_item_serializer_spec.rb
@@ -2,32 +2,96 @@
RSpec.describe DirectoryItemSerializer do
fab!(:user)
+ fab!(:directory_column) do
+ DirectoryColumn.create!(name: "topics_entered", enabled: true, position: 1)
+ end
+ fab!(:user_field_1) { Fabricate(:user_field, name: "user_field_1", searchable: true) }
+ fab!(:user_field_2) { Fabricate(:user_field, name: "user_field_2", searchable: false) }
before { DirectoryItem.refresh! }
- let :serializer do
- directory_item =
- DirectoryItem.find_by(user: user, period_type: DirectoryItem.period_types[:all])
- DirectoryItemSerializer.new(directory_item, { attributes: DirectoryColumn.active_column_names })
+ context "when serializing user fields" do
+ it "serializes user fields with searchable and non-searchable values" do
+ user.user_custom_fields.create!(name: "user_field_1", value: "Value 1")
+ user.user_custom_fields.create!(name: "user_field_2", value: "Value 2")
+
+ user_fields =
+ serialized_payload(
+ attributes: DirectoryColumn.active_column_names,
+ user_custom_field_map: {
+ "user_field_1" => user_field_1.id,
+ "user_field_2" => user_field_2.id,
+ },
+ searchable_fields: [user_field_1],
+ )
+
+ expect(user_fields).to eq(
+ user_field_1.id => {
+ value: ["Value 1"],
+ searchable: true,
+ },
+ user_field_2.id => {
+ value: ["Value 2"],
+ searchable: false,
+ },
+ )
+ end
+
+ it "handles multiple values for the same field" do
+ user.user_custom_fields.create!(name: "user_field_1", value: "Value 1")
+ user.user_custom_fields.create!(name: "user_field_1", value: "Another Value")
+
+ user_fields =
+ serialized_payload(
+ attributes: DirectoryColumn.active_column_names,
+ user_custom_field_map: {
+ "user_field_1" => user_field_1.id,
+ },
+ searchable_fields: [],
+ )
+
+ expect(user_fields[user_field_1.id]).to eq(
+ value: ["Value 1", "Another Value"],
+ searchable: false,
+ )
+ end
end
- it "Serializes attributes for enabled directory_columns" do
- DirectoryColumn.update_all(enabled: true)
+ context "when serializing directory columns" do
+ let :serializer do
+ directory_item =
+ DirectoryItem.find_by(user: user, period_type: DirectoryItem.period_types[:all])
+ DirectoryItemSerializer.new(
+ directory_item,
+ { attributes: DirectoryColumn.active_column_names },
+ )
+ end
- payload = serializer.as_json
- expect(payload[:directory_item].keys).to include(*DirectoryColumn.pluck(:name).map(&:to_sym))
+ it "serializes attributes for enabled directory_columns" do
+ DirectoryColumn.update_all(enabled: true)
+
+ payload = serializer.as_json
+ expect(payload[:directory_item].keys).to include(*DirectoryColumn.pluck(:name).map(&:to_sym))
+ end
+
+ it "doesn't serialize attributes for disabled directory columns" do
+ DirectoryColumn.update_all(enabled: false)
+ directory_column = DirectoryColumn.first
+ directory_column.update(enabled: true)
+
+ payload = serializer.as_json
+ expect(payload[:directory_item].keys.count).to eq(4)
+ expect(payload[:directory_item]).to have_key(directory_column.name.to_sym)
+ expect(payload[:directory_item]).to have_key(:id)
+ expect(payload[:directory_item]).to have_key(:user)
+ expect(payload[:directory_item]).to have_key(:time_read)
+ end
end
- it "Doesn't serialize attributes for disabled directory columns" do
- DirectoryColumn.update_all(enabled: false)
- directory_column = DirectoryColumn.first
- directory_column.update(enabled: true)
+ private
- payload = serializer.as_json
- expect(payload[:directory_item].keys.count).to eq(4)
- expect(payload[:directory_item]).to have_key(directory_column.name.to_sym)
- expect(payload[:directory_item]).to have_key(:id)
- expect(payload[:directory_item]).to have_key(:user)
- expect(payload[:directory_item]).to have_key(:time_read)
+ def serialized_payload(serializer_opts)
+ serializer = DirectoryItemSerializer.new(DirectoryItem.find_by(user: user), serializer_opts)
+ serializer.as_json.dig(:directory_item, :user, :user_fields)
end
end