aboutsummaryrefslogtreecommitdiff
path: root/pearl.el
diff options
context:
space:
mode:
Diffstat (limited to 'pearl.el')
-rw-r--r--pearl.el66
1 files changed, 54 insertions, 12 deletions
diff --git a/pearl.el b/pearl.el
index d6f14de..ca9d0b1 100644
--- a/pearl.el
+++ b/pearl.el
@@ -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 ()