googleapis / nodejs-talent

This repository is deprecated. All of its content and history has been moved to googleapis/google-cloud-node.
https://cloud.google.com/solutions/talent-solution/
Apache License 2.0
24 stars 11 forks source link

completeQuery generates an exception (Project id in request is invalid) #384

Closed alterschwede closed 2 years ago

alterschwede commented 2 years ago

There's no way to get the query completion (CompletionClient) to work.

I can create/delete/update companies and jobs. But wen I call client.completeQuery(request) I always get the same error:

Error: 3 INVALID_ARGUMENT: Project id in request is invalid.. Request ID for tracking: d95793dc-6a37-41f4-b25e-fdeaaf812373:APAb7IQd/7H4oMMqmswLgPCUeyuIbo2NRA==
    at Object.callErrorFromStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/call.js:31:26)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client.js:179:52)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:336:141)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:299:181)
    at /Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/call-stream.js:145:78
    at processTicksAndRejections (node:internal/process/task_queues:78:11) {
  code: 3,
  details: 'Project id in request is invalid.. Request ID for tracking: d95793dc-6a37-41f4-b25e-fdeaaf812373:APAb7IQd/7H4oMMqmswLgPCUeyuIbo2NRA==',
  metadata: Metadata { internalRepr: Map(0) {}, options: {} },
  note: 'Exception occurred in retry method that was not classified as transient',
  statusDetails: []

The only way to state the Project id in the call is using the tenant path and that exact tenant path is working for all my other calls to create/delete/update companies and jobs.

Further on, that exact tenant path is working when I try this (!!!): https://jobs.googleapis.com/v4/projects/insurancepunk/tenants/568e2f34-a3eb-4edb-aa77-eb6df97a6068:completeQuery?query=Berater&page_size=10

There must be some bug in the lib!

sofisl commented 2 years ago

Hi @alterschwede,

Can you provide your code sample to reproduce the bug? Thanks in advance!

alterschwede commented 2 years ago

It's quite simple actually and in the end almost the same as the snippet sample: https://github.com/googleapis/nodejs-talent/blob/main/samples/snippet/job_search_autocomplete_job_title.js

First things first. The fact that this REST call is wrking, says that my tenant name is ok. https://jobs.googleapis.com/v4/projects/insurancepunk/tenants/568e2f34-a3eb-4edb-aa77-eb6df97a6068:completeQuery?query=Berater&page_size=10

I generate my OAut here: https://developers.google.com/oauthplayground/ Stating the scope: https://www.googleapis.com/auth/jobs The request is fired using Postman. Everything works well!

Now to my code. It's a Next.js application.

Im using the latest package: "@google-cloud/talent": "*",

In the "package-lock.json" i can see that that im using version 3.5.1

    "@google-cloud/talent": {
      "version": "3.5.1",
      "resolved": "https://registry.npmjs.org/@google-cloud/talent/-/talent-3.5.1.tgz",
      "integrity": "sha512-ujfDN58CcnUC3NMf5H9xUT8nLN5w4JPbynDmHKCqPeyJjmK5uc1bpYTGMz/yjhCBLjsRVATJ6VWH8phRF2dlEA==",
      "requires": {
        "google-gax": "^2.24.1"
      }
    },

I'm developing on a Mac Book Pro with the M1 Apple silicon (macOS Big Sur 11.6)

Actually, the code shown below, is part of an API route in the Next.js application. I trigger the code using Postman, so there's practically no fancy code running before the function is called.

I Include the lib: const talent = require('@google-cloud/talent').v4;

This is my completer function:

  async function searchJobCompleterExternalAPI(search_data, tenantPath) {

    //was == search pattern
    let {
      was,
    } =  search_data;

    let promised_data = new Promise(function(resolve, reject) {
      const client = new talent.CompletionClient();
      const formattedParent = tenantPath; //client.tenantPath(projectId, tenantId);

      const query = was;  //Part of text 
      const numResults = 5;
      const languageCode = 'de';
      const languageCodes = [languageCode];

      const request = {
        parent: formattedParent,
        query: query,
        pageSize: numResults,
        languageCodes: languageCodes,
        scope: 'PUBLIC',
        type: 'COMBINED'
      };      

      let results = [];
      client.completeQuery(request)
      .then(responses => {
        const response = responses[0];
        for (const result of response.completionResults) {
          results.push({suggestion: result.suggestion, type: result.type});
          console.log(`Suggested title: ${result.suggestion}`);
          console.log(`Suggestion type: ${result.type}`);
        }
        resolve({data: results});
      })
      .catch(err => {
        console.error(err);
        resolve({data: []});
      });

    }) //Promise
    let result = await promised_data;
    return result;
  }

Let me explain some lines here.

The stringifyed request looks like this:

{
"parent":"projects/insurancepunk/tenants/568e2f34-a3eb-4edb-aa77-eb6df97a6068",
"query":"Berater",
"pageSize":5,
"languageCodes":["de"],
"scope":"PUBLIC",
"type":"COMBINED"
}

As you can see the tenant is the same as in my "OAut-Test".

This line tells that i have been doing somme testing with the tenant:

const formattedParent = tenantPath; //client.tenantPath(projectId, tenantId);

I'l explain that!

As I'm not storing the tenant id and always want to be sure to use the correct tenant name (the tenant path) I retrieve the tenant name using my external tenant id. This approach also makes sure the tenant is present, as there is no way to check if a tenant is actually present. This is the code:

async function checkTenantPresent(production) {
    //Return value of the fnktion:
    let tenantPresent = false;

    let promised_data = new Promise(function(resolve, reject) {
      //Check if tenant exist
      let tenantId = process.env.NEXT_NOT_PUBLIC_CTS_TENANT;
      if(!production) {
        tenantId += "_test";
      }
      const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;
      const client = new talent.TenantServiceClient();
      const formattedParent = client.projectPath(projectId);

      client.listTenants({parent: formattedParent})
      .then(responses => {
        const resources = responses[0];
        let tenantPath = "";
        let tenantExternalId = "";
        for (const resource of resources) {        
        //   console.log(`Tenant Name: ${resource.name}`);
        //   console.log(`External ID: ${resource.externalId}`);
          if (resource.externalId == tenantId) {
            tenantPresent = true;
            tenantPath = resource.name;
            tenantExternalId = resource.externalId;
          } 
        }
        resolve({data: {present: tenantPresent, tenantPath: tenantPath, tenantExternalId: tenantExternalId}});
      })
      .catch(err => {
        console.error(err);
        resolve({data: {present: tenantPresent, tenantPath: ""}});
      });

    }) //Promise
    let result = await promised_data;
    return result;
  }

Actually, I would love to see a solution to this in the Talent API.

As you can see, I fetch all the tenants for my project, comparing all external ID's with my ID:

if (resource.externalId == tenantId) {
...

When i find it, i return the tenant from the Google Talent API: tenantPath = resource.name

This way, i always use a tenant, coming directly from the Google Talent API!

Back to the first function shown above. Calling client.completeQuery(request) Is always ending in the catch, stating this error message:

Error: 3 INVALID_ARGUMENT: Project id in request is invalid.. Request ID for tracking: bcf8fa4f-d97b-4cb1-bf00-5b39d70267f1:APAb7IQ/f8YojHR7MeA3b7bKEyvzfsgWLA==
    at Object.callErrorFromStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/call.js:31:26)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client.js:180:52)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:336:141)
    at Object.onReceiveStatus (/Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:299:181)
    at /Users/stefankuhn/development/nextprojects/insurancepunk/node_modules/@grpc/grpc-js/build/src/call-stream.js:160:78
    at processTicksAndRejections (node:internal/process/task_queues:78:11) {
  code: 3,
  details: 'Project id in request is invalid.. Request ID for tracking: bcf8fa4f-d97b-4cb1-bf00-5b39d70267f1:APAb7IQ/f8YojHR7MeA3b7bKEyvzfsgWLA==',
  metadata: Metadata { internalRepr: Map(0) {}, options: {} },
  note: 'Exception occurred in retry method that was not classified as transient'
}

This error message is a bit strange, as there are no way to explicitly state the project id. The only way is using the tenant name, and as we now know, that one is ok.

BTW, a comment in the snippet sample mentioned above says: // const tenantId = 'Your Tenant ID (using tenancy is optional)'; I don't think this is correct, as the Node-Library generates an error if you don't state the tenant.

If you need more information, feel free to reach out!

As I said, I have successfully implemented a bunch of other functions, that work well.

I e.g. have a function for searching jobs, that's used exactly the same way. (Via a Next.js API route)

Here it is (without further comments):

  async function searchJobExternalAPI(search_data, tenantPath, enableBroadening, offset, maxPageSize) {

    let {
      was,
      wo,
      distanceInKm,
      uid,
      sessionid,
      domain
    } =  search_data;

    let promised_data = new Promise(function(resolve, reject) {
      const client = new talent.JobServiceClient();
      const formattedParent = tenantPath;
      const requestMetadata = {
        domain: domain,
        sessionId: sessionid,
        userId: uid,
      };

      let jobQuery = {
        query: was
      };

      let distanceInMiles = 0;
      switch (distanceInKm) {
        case "0 km":
          distanceInMiles = 0;
          break;        
        case "10 km":
          distanceInMiles = 6;
          break;
        case "20 km":
          distanceInMiles = 12;
          break;
        case "30 km":
          distanceInMiles = 19;
          break;
        case "40 km":
          distanceInMiles = 25;
          break;
        case "50 km":
          distanceInMiles = 31;
          break;
        default:
          distanceInMiles = 0;
          break;
      }

      if(wo != null && wo != '') {
        jobQuery['locationFilters'] = [ { address: wo, distanceInMiles: distanceInMiles} ];
      }

      const request = {
        searchMode: 'JOB_SEARCH',
        requestMetadata: requestMetadata,
        jobQuery: jobQuery,
        enableBroadening: enableBroadening,
        jobView: 'JOB_VIEW_FULL',
        offset: offset,
        maxPageSize: maxPageSize,
        parent: formattedParent
      };

      //console.log(`Job request: ${JSON.stringify(request)}`);

      client.searchJobs(request)
      .then(responses => {
        const resources = responses[0];
        // Equals '' if there are no more results!
        //The documentation doesn't reveals what it contains in the other cases...
        //console.log(`nextPageToken: ${resources.nextPageToken}`); 
        resolve({
          data: {
            nextPageToken: resources.nextPageToken, 
            offset: offset, 
            maxPageSize: maxPageSize, 
            matchingJobs: resources.matchingJobs
          }
        });
      })
      .catch(err => {
        console.error(err);
        resolve({data: {}});
      });

    }) //Promise
    let result = await promised_data;
    return result;    
  }
alterschwede commented 2 years ago

I changed the title of my post, to make it more clear, what the post is all about.

chingor13 commented 2 years ago

Can you try changing parent in the request object to tenant? It looks like the request object you're building might be using the wrong param name for ICompleteQueryRequest.

      const request = {
        // parent: formattedParent,
        tenant: formattedParent,
        query: query,
        pageSize: numResults,
        languageCodes: languageCodes,
        scope: 'PUBLIC',
        type: 'COMBINED'
      };      

      let results = [];
      client.completeQuery(request)
chingor13 commented 2 years ago

Note: I also filed an internal bug against our reference documentation because in the Node.js client docs, the ICompleteQueryRequest proto object is not linked or findable.

alterschwede commented 2 years ago

Thanks a lot! That solved the problem! :-)

You have to correct the sample snippet code too, as it says:

  const client = new talent.CompletionClient();
  // const projectId = 'Your Google Cloud Project ID';
  // const tenantId = 'Your Tenant ID (using tenancy is optional)';
  // const query = '[partially typed job title]';
  // const numResults = 5;
  // const languageCode = 'en-US';
  const formattedParent = client.tenantPath(projectId, tenantId);
  const languageCodes = [languageCode];
  const request = {
    parent: formattedParent,
    query: query,
    pageSize: numResults,
    languageCodes: languageCodes,
  };

Here is a link to the sample:

https://github.com/googleapis/nodejs-talent/blob/main/samples/snippet/job_search_autocomplete_job_title.js