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

Allow configuration of sorting for Project Lists #15288

Merged
merged 25 commits into from May 8, 2024
Merged

Conversation

klaustopher
Copy link
Contributor

@klaustopher klaustopher force-pushed the projects-list-sort branch 6 times, most recently from c54a2cd to 14faf9a Compare April 24, 2024 06:01
@klaustopher klaustopher force-pushed the projects-list-sort branch 6 times, most recently from 6c48d96 to d1b6dc6 Compare April 30, 2024 12:54
@klaustopher klaustopher marked this pull request as ready for review May 7, 2024 13:37
@ulferts ulferts self-requested a review May 7, 2024 14:07
Copy link
Contributor

@ulferts ulferts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code structure is heading into the right direction. Unfortunately I found a larger number of issues that need to be worked on before merging.

From a user perspective I struggled with removing a sorting. It took me a while to understand that I could set it to the blank value again. This is how I see it specified as well but it felt akward.

  • Brush up design of component by adding descriptive text, placeholder and margin:

Specification:
image

https://www.figma.com/file/YCCMdJWkrtP9YSmf49Od0i/Project-lists?type=design&node-id=1830-33701&mode=design&t=F9bkDN6kccdsRZxi-0

Implementation:
image

  • Persist sorting when saving a list. Currently the sorting falls back to the "Project hierarchy"

  • Add feature spec that covers both the changing of unpersisted lists as well as saving the sort order upon persisting a list and then querying for it again e.g. in: https://github.com/opf/openproject/blob/dev/spec/features/projects/persisted_lists_spec.rb and https://github.com/opf/openproject/blob/dev/spec/features/projects/projects_index_spec.rb

  •  Sorting by "Project hierarchy" is currently mutually exclusive with sorting by any other column. The reason for this is the inability to sort by a second/third argument on each level. That is why the projects table upon clicking on the hierarchy sorting trigger resets the sorting to only work with the "Project hierarchy".

  • Sorting by "Project hierarchy" only makes sense in a single direction. When having selected that option, the "Asc"/"Desc" toggle should be removed I think.

  • When exporting a project list (e.g. to PDF) the sort order should be applied as well.

@selectable_columns ||= [
{ id: :lft, name: I18n.t(:label_project_hierarchy) }
] + helpers.projects_columns_options
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method would be better placed in the SortByComponent since that component is the only one using the method. It could then even become a private method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it cannot be part of the SortByComponent as this component does not know that it is used in a project query context. So we need to pass in the selectable columns and this place is the right place, as it is the last component in the hierarchy that knows that it is dealing with a ProjectQuery

@@ -29,11 +33,20 @@
dragAreaLabel: I18n.t(:'queries.configure_view.columns.drag_area_label'),
appendToComponent: true
}%>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the SortByComponent is in, it would make sense to extract the part about the columns into its own component that could then also be reused in the administration. There wouldn't be too much template code in it but a number of helper methods could be moved into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this PR about implementing the sorting functionality? Extracting the column stuff is a perfect candidate for a Code maintenance follow up ticket. I'd like not to extend the range of this PR as it already is way to much for what it does.


def current_orders
JSON.dump(query.orders.map { |order| [order.attribute, order.direction] })
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code serializes the query's order so that it can be placed in the form. I guess using the params-from-query controller will not work since the values would be overwritten. And I am still unsure about whether the js based approach turns out to be a good one (currently also discussing with @toy).

But if we go this way, then I'd think extracting the serialization into a shared helper right away would make sense as I would then expect the functionality to be reused time and again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the JS code which does the same, do we need to have this code in here at all? We could let the JS set the value upon initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This serializes the queries orders to the exact format how they need to be added to the query string. Can we PLEASE PLEASE PLEASE, not throw a reformatting of the query string in this PR, as we did in other places. We can do that in a follow up and change the serialization format ... But I don't know why we should ...

# Keys from the order can be symbols, strings or regexes
selectable_columns.select do |column_option|
all_order_keys.any? { |order_key| order_key === column_option[:id] }
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move the code finding the available orders into the Queries::Orders::AvailableOrders as that reduces the interface exposed and only keep the part about the interception between the orders and the columns in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work as for example the lft virtual column is not mentioned in the selects, so it would not work there.

declare readonly inputRowContainerTarget:HTMLElement;

connect():void {
this.inputRowTargets.forEach((row) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to also use inputRowTargetConnected. Not necessary, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would I want to do this. As we are dealing with multiple targets here, the TargetConnected method would fire 3 times and thus this code would be executed 3 times.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion was to use the inputRowTargetConnected not with this.inputRowTargets.forEach but rather with only the single row that is connected when the element is called. In either case, it was just a suggestion.

@klaustopher
Copy link
Contributor Author

Bildschirmfoto 2024-05-07 um 19 31 01

I don't know how you got the screenshot you posted, but there is definetly spacing, so I don't know what commit you tested. I'll add the headlines. But the way how the dropdowns are styled are how the primer component looks. There is no chevron indicator, and I don't think we should apply a special styling here...

@klaustopher
Copy link
Contributor Author

Sorting by "Project hierarchy" is currently mutually exclusive with sorting by any other column. The reason for this is the inability to sort by a second/third argument on each level. That is why the projects table upon clicking on the hierarchy sorting trigger resets the sorting to only work with the "Project hierarchy".

Sorting by "Project hierarchy" only makes sense in a single direction. When having selected that option, the "Asc"/"Desc" toggle should be removed I think.

This is a bit annoying as we are already doing special things like adding the lft as a sortable column, because it is nowhere defeined. Now we also need to treat it specially and remove the order buttons. Could we make the sorting by lft somehow explicit, and define inside of the Order class what directions it supports and then forward this info to the frontend? Also, in the WorkPackages Angular view, it's not even possible to add the hierarchy sorting in the Sort By dialog

@klaustopher
Copy link
Contributor Author

klaustopher commented May 8, 2024

When exporting a project list (e.g. to PDF) the sort order should be applied as well.

The sortBy params are correctly forwarded to the export, here's an example:

Opening

http://localhost:3000/projects?columns=name+created_at+public+cf_13&sortBy=[["cf_13","desc"],["name","desc"]]

I see that the export link correctly points to

http://localhost:3000/projects.csv?columns=name+created_at+public+cf_13&sortBy=[["cf_13","desc"],["name","desc"]]

So that looks correct. I added a debug output to the Projects::ExportJob and when I output the query there, the orders param gets converted into a hash and it has the wrong order in there:

worker  | ****************************************************************************************************
worker  | #<Queries::Projects::Orders::NameOrder:0x000000032cf5af08>
worker  | #<Queries::Projects::Orders::CustomFieldOrder:0x000000032cf5a8a0>
worker  | ****************************************************************************************************

So, something goes wrong when the sortBy params are converted to the order for the export job. I'll take a look what's going wrong internally there. But that should be a problem that has already been in the code for exporting code.


Also, looking at the job params that get stored in the database, we already have a wrong order:

{
  "job_id": "2277295d-025e-4383-8dfe-e436e8c0022e",
  "locale": "en",
  "priority": 7,
  "timezone": "UTC",
  "arguments": [
    {
      "user": {
        "_aj_globalid": "gid://open-project/User/4"
      },
      "query": {
        "user": {
          "_aj_globalid": "gid://open-project/User/4"
        },
        "orders": {
          "name": {
            "value": "desc",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          },
          "cf_13": {
            "value": "desc",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          },
          "_aj_symbol_keys": [
          ]
        },
        "filters": [
          {
            "name": {
              "value": "active",
              "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
            },
            "values": [
              "t"
            ],
            "operator": "=",
            "_aj_symbol_keys": [
              "name",
              "operator",
              "values"
            ]
          }
        ],
        "selects": [
          {
            "value": "name",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          },
          {
            "value": "created_at",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          },
          {
            "value": "public",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          },
          {
            "value": "cf_13",
            "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
          }
        ],
        "group_by": null,
        "_aj_symbol_keys": [
          "filters",
          "orders",
          "group_by",
          "selects",
          "user"
        ]
      },
      "export": {
        "_aj_globalid": "gid://open-project/Projects::Export/7"
      },
      "mime_type": {
        "value": "csv",
        "_aj_serialized": "ActiveJob::Serializers::SymbolSerializer"
      },
      "_aj_ruby2_keywords": [
        "export",
        "user",
        "mime_type",
        "query"
      ]
    }
  ],
  "job_class": "Projects::ExportJob",
  "executions": 0,
  "queue_name": "default",
  "enqueued_at": "2024-05-08T06:41:44.632141000Z",
  "scheduled_at": null,
  "provider_job_id": null,
  "exception_executions": {
  }
}

Copy link
Contributor

@ulferts ulferts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All is fine now functionality wise. I'd still prefer some changes to the code but yes, we can also do it in subsequent PRs.

@ulferts
Copy link
Contributor

ulferts commented May 8, 2024

Good job on digging up the reason for the export problems, @klaustopher

@ulferts ulferts merged commit cb13a32 into dev May 8, 2024
9 of 10 checks passed
@ulferts ulferts deleted the projects-list-sort branch May 8, 2024 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants