FusionAuth / fusionauth-typescript-client

A TypeScript client for FusionAuth
https://fusionauth.io
Apache License 2.0
60 stars 27 forks source link

Unexpected Response for `FusionAuthClient#searchUsersByQuery` When Using `sortFields`. #96

Open tabuckner opened 6 months ago

tabuckner commented 6 months ago

Summary

There might be some sort of configuration step that I've overlooked, or some sort of misunderstanding in how things are supposed to work. I'm running into a strange issue wherein adding a value to the parameter object for FusionAuthClient#searchUsersByQuery yields a result set smaller than the same query omitting sortFields.

I had a call with our sales engineer, and we've sent a few emails, but they've now suggested that I open a ticket in the TS repo.

Expected Behavior

Each of the specified parameter values should result in a result set size of 5. When providing the sortFields the results should be sorted accordingly, assuming the provided values are valid.

Actual Behavior

Adding sortFields to an existing { search } parameter property yields a result set that is smaller than the same { search } without the sortFields.

Copied Notes from Email Thread with Sales Engineering

Scenario 1 - Wildcard Query w/o Sort Fields

searchUsersByQuery Param Object
{
  "search": {
    "queryString": "*",
    "accurateTotal": true,
    "startRow": 0,
    "numberOfResults": 5
  }
}
Result
{
  "statusCode": 200,
  "response": {
    "expandable": [],
    "nextResults": "eyJscyI6WyIxLjA4NzAxMTMiLG51bGwsInRidWNrbmVyK3Rlc3QtMTZAbGlxdWliYXNlLmNvbSIsIjQ3ZjIxNGZiLTkyZGQtNDBjYy04ZTcyLTliZjZiZGEzMGU4OCJdLCJxcyI6IioiLCJzZiI6W119",
    "total": 31,
    "users": [
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+test-3@liquibase.com",
        "firstName": "",
        "id": "c7b16346-9df3-4583-9934-9935ffe62803",
        "insertInstant": 1703711630363,
        "lastLoginInstant": 1703711630392,
        "lastName": "",
        "lastUpdateInstant": 1703711630363,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1703711630369,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "99dd7b31-cfd0-419f-9a05-567f50c9c696",
            "insertInstant": 1703711630392,
            "lastLoginInstant": 1703711630392,
            "lastUpdateInstant": 1703711630392,
            "preferredLanguages": [],
            "roles": ["view_only"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1703711630392
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1703711630363
      },
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+test-2@liquibase.com",
        "firstName": "",
        "id": "8601a3b2-c63a-4821-b220-9e2dbba21b60",
        "insertInstant": 1703711622723,
        "lastLoginInstant": 1703711622784,
        "lastName": "",
        "lastUpdateInstant": 1703711622723,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1703711622728,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "59a23252-4bcb-43db-91c6-dbdf11e5c510",
            "insertInstant": 1703711622784,
            "lastLoginInstant": 1703711622784,
            "lastUpdateInstant": 1703711622784,
            "preferredLanguages": [],
            "roles": ["view_only"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1703711622784
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1703711622723
      },
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+test-23@liquibase.com",
        "firstName": "",
        "id": "22601536-f467-4352-a4d1-fbb737298146",
        "insertInstant": 1703711731279,
        "lastLoginInstant": 1703711731328,
        "lastName": "",
        "lastUpdateInstant": 1703711731279,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1703711731284,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "7a6513d0-47ac-41dc-b8c5-c3fc30dac7f7",
            "insertInstant": 1703711731328,
            "lastLoginInstant": 1703711731328,
            "lastUpdateInstant": 1703711731328,
            "preferredLanguages": [],
            "roles": ["view_only"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1703711731328
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1703711731279
      },
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+test-22@liquibase.com",
        "firstName": "",
        "id": "36352967-2b86-419f-b776-ce177d84a631",
        "insertInstant": 1703711727403,
        "lastLoginInstant": 1703711727569,
        "lastName": "",
        "lastUpdateInstant": 1703711727403,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1703711727418,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "b69fd376-08e3-4d4f-916c-11ade389477d",
            "insertInstant": 1703711727569,
            "lastLoginInstant": 1703711727569,
            "lastUpdateInstant": 1703711727569,
            "preferredLanguages": [],
            "roles": ["view_only"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1703711727569
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1703711727403
      },
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+test-16@liquibase.com",
        "firstName": "",
        "id": "47f214fb-92dd-40cc-8e72-9bf6bda30e88",
        "insertInstant": 1703711696599,
        "lastLoginInstant": 1703711696686,
        "lastName": "",
        "lastUpdateInstant": 1703711696599,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1703711696613,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "fd4000ad-2a9c-4f51-a511-6229c5c8fbc1",
            "insertInstant": 1703711696686,
            "lastLoginInstant": 1703711696686,
            "lastUpdateInstant": 1703711696686,
            "preferredLanguages": [],
            "roles": ["view_only"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1703711696686
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1703711696599
      }
    ]
  }
}
NOTES

In this example I get a total count of 31, and a full page of results with the page size I requested. However, when passing a similar param object to searchUsersByQuery that introduces "sortFields", the results are changed in an unexpected way.

Scenario 2 - Wildcard Query w/ Sort Fields

searchUsersByQuery Param Object
{
  "search": {
    "queryString": "*",
    "startRow": 0,
    "numberOfResults": 5,
    "sortFields": [
      {
        "name": "email",
        "order": "asc",
        "missing": "_last"
      }
    ]
  }
}
Result
{
  "statusCode": 200,
  "response": {
    "expandable": [],
    "nextResults": "eyJscyI6WyJ0YnVja25lcitwb3N0bWFuLXRlc3RAbGlxdWliYXNlLmNvbSIsIjFhNWJjZGUwLTAyMjMtNDQyMy1hMDQ1LTAyY2U2Yjg5MjAyZiJdLCJxcyI6IioiLCJzZiI6W3sibWlzc2luZyI6Il9sYXN0IiwibmFtZSI6ImVtYWlsIiwib3JkZXIiOiJhc2MifV19",
    "total": 31,
    "users": [
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {
          "Company": "Liquibase"
        },
        "email": "tbuckner+admin@liquibase.com",
        "id": "b81def82-3ada-4cfe-b252-a6e5953481e3",
        "insertInstant": 1701379354082,
        "lastLoginInstant": 1701379354100,
        "lastUpdateInstant": 1701379354082,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1701379354095,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "3c219e58-ed0e-4b18-ad48-f4f92793ae32",
            "data": {},
            "id": "4412d600-8a31-439d-909e-5eaa502ab8d5",
            "insertInstant": 1701379354100,
            "lastLoginInstant": 1701379354100,
            "lastUpdateInstant": 1701379354100,
            "preferredLanguages": [],
            "roles": ["admin"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1701379354100
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1701379354082
      },
      {
        "active": true,
        "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
        "data": {},
        "email": "tbuckner+postman-test@liquibase.com",
        "id": "1a5bcde0-0223-4423-a045-02ce6b89202f",
        "insertInstant": 1702932995198,
        "lastUpdateInstant": 1702997334600,
        "memberships": [],
        "passwordChangeRequired": false,
        "passwordLastUpdateInstant": 1702933233666,
        "preferredLanguages": [],
        "registrations": [
          {
            "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
            "data": {},
            "id": "23bd2ae6-33fe-41dd-a9a4-3af44c2a5fb4",
            "insertInstant": 1702932995486,
            "lastLoginInstant": 1702996132116,
            "lastUpdateInstant": 1702997334641,
            "preferredLanguages": [],
            "roles": ["customer_admin", "editor"],
            "tokens": {},
            "usernameStatus": "ACTIVE",
            "verified": true,
            "verifiedInstant": 1702932995486
          }
        ],
        "tenantId": "d7d09513-a3f5-401c-9685-34ab6c552453",
        "twoFactor": {
          "methods": [],
          "recoveryCodes": []
        },
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1702932995198
      }
    ]
  }
}

Expected

I would see the >2 records as the result of this query, presumably sorted by the supplied field. Previously, without the sortFields property, I was able to retrieve >2 records from Fusion Auth. Somehow, introducing the "sortField" has 'filtered' the results to only return 2 of the reported 31 records. Without the sortField, I'm able to retrieve the expected records.

Specifications

Video of Issue

https://github.com/FusionAuth/fusionauth-typescript-client/assets/32392635/7c6132d3-615d-4788-9032-27033c3f5937

NOTE

Note that the only difference is introducing the sortFields property. Is the issue the sort direction? I believe it's a string enum, so I assumed passing the string value assigned in the enum is fine. Perhaps that's the issue?

matt1hathcock commented 6 months ago

@tabuckner can you share how you were able to get those values for email into your FusionAuth environment? Seems like you may have done this in importing the users. Any reason why you did the markdown text?

tabuckner commented 6 months ago

@matt1hathcock It looks like there was some sort of formatting issue when copying the values from our email thread into this ticket. I've now updated the original comment with the correct responses from the SDK.

tabuckner commented 6 months ago

As requested in our email thread, here's a code snippet that will yield the results I'm seeing.

To note, I believe this code snippet will not provide much value as the issue seems to be environment related. Aside from that, there is still a strong chance that there is some sort of user error on my end.

import { FusionAuthClient, Sort } from "@fusionauth/typescript-client";

const FA_API_KEY = "my-super-secret-API-key";
const FA_API_SERVER_URL = "my-super-secret-API-server-url";
const FA_RESULT_SIZE = 5;

const myFAClient = new FusionAuthClient(FA_API_KEY, FA_API_SERVER_URL);

const testCase1Params = {
  search: {
    queryString: "*",
    accurateTotal: true,
    numberOfResults: FA_RESULT_SIZE,
  },
};

const testCase2Params = {
  search: {
    queryString: "*",
    accurateTotal: true,
    numberOfResults: FA_RESULT_SIZE,
    sortFields: [{ name: "email", order: Sort.asc }],
  },
};

const test = async () => {
  const testCase1 = await myFAClient.searchUsersByQuery(testCase1Params);
  const testCase2 = await myFAClient.searchUsersByQuery(testCase2Params);

  console.warn(testCase1.response.total === testCase2.response.total); // false
};

test();