diff options
Diffstat (limited to 'pearl.el')
| -rw-r--r-- | pearl.el | 66 |
1 files changed, 54 insertions, 12 deletions
@@ -1581,13 +1581,41 @@ gone\" apart from \"the API call failed.\"" (funcall callback (or issue :missing))))) (lambda (_error _response _data) (funcall callback :error)))) +(defconst pearl--default-issue-sort + (vector '(("updatedAt" ("order" . "Descending")))) + "Fallback issue sort for a Custom View whose configured ordering pearl can't +reproduce: most-recently-updated first, matching the default `pearl-list-issues' +uses. Keeps a view from falling through to Linear's createdAt-desc API default, +which reads as random next to the rest of pearl.") + +(defconst pearl--view-sortable-orderings + '("priority" "estimate" "title" "label" "slaStatus" "createdAt" "updatedAt" + "completedAt" "dueDate" "accumulatedStateUpdatedAt" "cycle" "milestone" + "assignee" "project" "team" "manual" "workflowState") + "Linear `viewOrdering' values that also exist as `IssueSortInput' keys, so +pearl can reproduce the view's sort server-side. An ordering outside this set +falls back to `pearl--default-issue-sort'.") + +(defun pearl--view-sort-arg (ordering direction) + "Translate a Custom View's ORDERING and DIRECTION into an `IssueSortInput' list. +ORDERING is Linear's `viewOrdering' string; DIRECTION is `viewOrderingDirection' +(\"asc\", \"desc\", or nil). Returns a one-element vector usable as the `sort' +query variable, or nil when ORDERING isn't one pearl can reproduce -- the caller +then falls back to `pearl--default-issue-sort'. A nil DIRECTION defaults to +Descending, the field's natural order and what Linear's UI shows when no +direction is set." + (when (member ordering pearl--view-sortable-orderings) + (let ((order (if (equal direction "asc") "Ascending" "Descending"))) + (vector (list (cons ordering (list (cons "order" order)))))))) + (defun pearl--view-issues-query () "GraphQL query running a Custom View's own filter server-side, by view id. Pulls each issue's recent comments (see `pearl--list-comments-fragment') so a -view-populated list renders them." - (format "query ViewIssues($id: String!, $first: Int!, $after: String) { +view-populated list renders them. The `sort' variable orders the issues; see +`pearl--view-sort-arg' and `pearl--default-issue-sort'." + (format "query ViewIssues($id: String!, $first: Int!, $after: String, $sort: [IssueSortInput!]) { customView(id: $id) { - issues(first: $first, after: $after) { + issues(first: $first, after: $after, sort: $sort) { nodes { id identifier title description priority url updatedAt state { id name type } @@ -1603,16 +1631,21 @@ view-populated list renders them." } }" (pearl--list-comments-fragment))) -(defun pearl--query-view-async (view-id callback) +(defun pearl--query-view-async (view-id callback &optional sort) "Run the Custom View VIEW-ID server-side, calling CALLBACK with a query-result. The view applies its own stored filter on Linear's side; issues come back as -raw nodes (normalized at the render boundary), paged like the general fetch." - (let ((page-fn +raw nodes (normalized at the render boundary), paged like the general fetch. +SORT is an `IssueSortInput' list (the `sort' variable); when nil the view runs +under `pearl--default-issue-sort' so its order matches the rest of pearl rather +than Linear's createdAt-desc API default." + (let* ((sort (or sort pearl--default-issue-sort)) + (page-fn (lambda (after page-cb) (pearl--graphql-request-async (pearl--view-issues-query) `(("id" . ,view-id) ("first" . 100) + ("sort" . ,sort) ,@(when after (list (cons "after" after)))) (lambda (response) (if (or (null response) @@ -1647,14 +1680,17 @@ Selects no `url' -- Linear's `CustomView' type has none, and requesting it 400s the whole query (see `pearl--view-url' for how the link is rebuilt)." "query CustomViews($first: Int!, $after: String) { customViews(first: $first, after: $after) { - nodes { id name description shared team { id } } + nodes { id name description shared team { id } + viewPreferencesValues { viewOrdering viewOrderingDirection } } pageInfo { hasNextPage endCursor } } }") (defun pearl--custom-views (&optional force) "Return the workspace's Custom Views, fetching once and caching. -Each node carries id/name/description/shared/team. A non-nil FORCE refetches." +Each node carries id/name/description/shared/team plus viewPreferencesValues +(the configured ordering, read by `pearl--view-sort-arg'). A non-nil FORCE +refetches." (or (and (not force) pearl--cache-views) (let* ((response (pearl--graphql-request (pearl--custom-views-query) '(("first" . 100)))) @@ -6246,7 +6282,8 @@ commands. Errors if no source is recorded." merge)) ('view (pearl--progress "Refreshing %s..." (pearl--source-name source)) - (pearl--query-view-async (plist-get source :id) merge)) + (pearl--query-view-async (plist-get source :id) merge + (plist-get source :sort))) (_ (user-error "Unknown Linear source type: %s" (plist-get source :type))))))) ;;;###autoload @@ -6265,12 +6302,17 @@ dirty-buffer guard) and records the view as the active source." (view-id (and view (cdr (assoc 'id view))))) (unless view-id (user-error "No Custom View named %s" view-name)) - (let ((source (list :type 'view :name view-name :id view-id - :url (pearl--view-url view-id)))) + (let* ((prefs (cdr (assoc 'viewPreferencesValues view))) + (sort (pearl--view-sort-arg (cdr (assoc 'viewOrdering prefs)) + (cdr (assoc 'viewOrderingDirection prefs)))) + (source (list :type 'view :name view-name :id view-id + :url (pearl--view-url view-id) + :sort sort))) (pearl--progress "Running view %s..." view-name) (pearl--query-view-async view-id - (lambda (result) (pearl--render-query-result result source)))))) + (lambda (result) (pearl--render-query-result result source)) + sort)))) ;;;###autoload (defun pearl-open-current-view-in-linear () |
