NOTE: This bug report is for JIRA Service Desk Server. Using JIRA Service Desk Cloud? See the corresponding bug report.

      Summary

      The Pagination used in JIRA Service Desk REST API does not return consistent results

      Environment

      JIRA Service Desk REST API

      Steps to Reproduce

      1. Create a GET request for Customer Requests
        <baseURL>/rest/api-name/resource-name?start=0&limit=100
        
      2. Observe the "size" from the result
        Results
          "size": 76,
          "start": 0,
          "limit": 100,
          "isLastPage": true,
        
      3. Change the start to a higher value and re-send the GET request
      4. <baseURL>/rest/servicedeskapi/request?start=20&limit=100
        
      5. Observe the "size" from the result
        Results
          "size": 36,
          "start": 20,
          "limit": 100,
          "isLastPage": true,
        
      6. Change the start to a higher value and re-send the GET request
      7. <baseURL>/rest/servicedeskapi/request?start=30&limit=100
        
      8. Observe the "size" from the result
        Results
          "size": 16,
          "start": 30,
          "limit": 100,
          "isLastPage": true,
        

      Expected Results

      From the above example, Pagination should return 56 and 46 respectively

      Actual Results

      Returned values are not 56 and 46 respectively

      Notes

      JIRA Service Desk REST API is still an experimental release as per this comment

      Workaround

      It seems that the Servicedesk api code actually skips “start” parameter amount of entires twice. So, if you add start=10, its actually doing start=20 internally. If you say start=100, its actually doing start=200.
      In order to get it working correctly, you would need to use half of the value of the start parameter that you intend to use. For instance, I tried this : 
       

      http://localhost:8080/jira792/rest/servicedeskapi/request?start=0&limit=10

      and then

      http://localhost:8080/jira792/rest/servicedeskapi/request?start=5&limit=10

            [JSDSERVER-4187] Inconsistent JIRA Service Desk REST API Pagination

            Suddha made changes -
            Remote Link Original: This issue links to "JSDS-2537 (Bulldog)" [ 377156 ] New: This issue links to "JSMDC-2537 (JIRA Server (Bulldog))" [ 377156 ]
            Owen made changes -
            Workflow Original: JSD Bug Workflow v5 - TEMP [ 2304711 ] New: JAC Bug Workflow v3 [ 3126516 ]
            Status Original: Done [ 10044 ] New: Closed [ 6 ]
            Kamil Kolonko made changes -
            Resolution New: Fixed [ 1 ]
            Status Original: Awaiting Release [ 11372 ] New: Done [ 10044 ]
            Owen made changes -
            Symptom Severity Original: Minor [ 14432 ] New: Severity 3 - Minor [ 15832 ]
            Deeksha Singh (Inactive) made changes -
            Fix Version/s New: 4.0.0 [ 81612 ]
            Deeksha Singh (Inactive) made changes -
            Status Original: In Progress [ 3 ] New: Awaiting Release [ 11372 ]
            SET Analytics Bot made changes -
            Support reference count Original: 3 New: 4
            SET Analytics Bot made changes -
            UIS New: 1

            I found the issue in JSD codebase. Using sources decompiled by Intellij IDEA, but it gives you the idea.

            Let's say there are 70 requests for the user, and I'm running a query with start=50. That means there should be 20 requests on the page. Now, see comments in code below.

            ServiceDeskCustomerRequestServiceImpl:

            public Either<AnError, PagedResponse<CustomerRequest>> getCustomerRequests(@Nullable ApplicationUser user, 
                                                                                       @Nonnull CustomerRequestQuery customerRequestQuery) {
                Assertions.notNull("customerRequestQuery", customerRequestQuery);
                return Steps.begin(this.userFactory.wrap(user)).then((cu) -> {
                    return this.dispatchCustomerRequestQuery(cu.forJIRA(), customerRequestQuery);
                }).yield((cu, customerRequests) -> {
                    return customerRequests;
                }).map((input) -> {
                    // At this point input is a list of 20 CustomRequests
                    return PagedResponseImpl.toPagedResponse(customerRequestQuery.pagedRequest(), input);
                });
            }
            

            PagedResponseImpl:

            public static <T> PagedResponse<T> toPagedResponse(LimitedPagedRequest limitedPagedRequest, List<T> items) {
                // items has 20 entries, limitedPageRequest.start=50
                return filteredPageResponse(limitedPagedRequest, items, Predicates.alwaysTrue());
            }
            
            public static <T> PagedResponse<T> filteredPageResponse(LimitedPagedRequest limitedPagedRequest,
                                                                    List<T> items,
                                                                    Predicate<? super T> predicate) {
                if (predicate == null) {
                    predicate = Predicates.alwaysTrue();
                }
            
                boolean hasMore = items.size() > limitedPagedRequest.getStart() + limitedPagedRequest.getLimit();
                // This takes the iterable of 20 items and skips 50, so the result is empty. BAM!
                List<T> filteredItems = FluentIterable.from(items).skip(limitedPagedRequest.getStart())
                                                      .limit(limitedPagedRequest.getLimit()).filter(predicate).toList();
                return from(filteredItems, hasMore).pageRequest(limitedPagedRequest).build();
            }
            

            One way to fix this to replace the call in getCustomerRequests:

            // From:
            return PagedResponseImpl.toPagedResponse(customerRequestQuery.pagedRequest(), input);
            // To:
            LimitedPagedRequest pagedRequest = customerRequestQuery.pagedRequest();
            boolean hasMore = input.size() > pagedRequest.getStart() + pagedRequest.getLimit();
            return PagedResponseImpl.from(input, hasMore).pageRequest(pagedRequest).build();
            

            Konrad Garus added a comment - I found the issue in JSD codebase. Using sources decompiled by Intellij IDEA, but it gives you the idea. Let's say there are 70 requests for the user, and I'm running a query with start=50. That means there should be 20 requests on the page. Now, see comments in code below. ServiceDeskCustomerRequestServiceImpl : public Either<AnError, PagedResponse<CustomerRequest>> getCustomerRequests(@Nullable ApplicationUser user, @Nonnull CustomerRequestQuery customerRequestQuery) { Assertions.notNull( "customerRequestQuery" , customerRequestQuery); return Steps.begin( this .userFactory.wrap(user)).then((cu) -> { return this .dispatchCustomerRequestQuery(cu.forJIRA(), customerRequestQuery); }).yield((cu, customerRequests) -> { return customerRequests; }).map((input) -> { // At this point input is a list of 20 CustomRequests return PagedResponseImpl.toPagedResponse(customerRequestQuery.pagedRequest(), input); }); } PagedResponseImpl : public static <T> PagedResponse<T> toPagedResponse(LimitedPagedRequest limitedPagedRequest, List<T> items) { // items has 20 entries, limitedPageRequest.start=50 return filteredPageResponse(limitedPagedRequest, items, Predicates.alwaysTrue()); } public static <T> PagedResponse<T> filteredPageResponse(LimitedPagedRequest limitedPagedRequest, List<T> items, Predicate<? super T> predicate) { if (predicate == null ) { predicate = Predicates.alwaysTrue(); } boolean hasMore = items.size() > limitedPagedRequest.getStart() + limitedPagedRequest.getLimit(); // This takes the iterable of 20 items and skips 50, so the result is empty. BAM! List<T> filteredItems = FluentIterable.from(items).skip(limitedPagedRequest.getStart()) .limit(limitedPagedRequest.getLimit()).filter(predicate).toList(); return from(filteredItems, hasMore).pageRequest(limitedPagedRequest).build(); } One way to fix this to replace the call in getCustomerRequests : // From: return PagedResponseImpl.toPagedResponse(customerRequestQuery.pagedRequest(), input); // To: LimitedPagedRequest pagedRequest = customerRequestQuery.pagedRequest(); boolean hasMore = input.size() > pagedRequest.getStart() + pagedRequest.getLimit(); return PagedResponseImpl.from(input, hasMore).pageRequest(pagedRequest).build();

            This is a disastrous show-stopper, not a low-priority bug that deserves no attentions for years. Since the assignee is showing as inactive, does it mean it will take 2 more years for someon to notice it again?

            Konrad Garus added a comment - This is a disastrous show-stopper, not a low-priority bug that deserves no attentions for years. Since the assignee is showing as inactive, does it mean it will take 2 more years for someon to notice it again?

              mwadhwa@atlassian.com Milap Wadhwa (Inactive)
              cchan Chung Park Chan
              Affected customers:
              10 This affects my team
              Watchers:
              15 Start watching this issue

                Created:
                Updated:
                Resolved: