Link several entities to another entity

Hi, I recently opened a thread on using Public Id integration with gitlab and I’ve been studying the documentation about relation auto-linking

And I came to the conclusion that the existing solution does not suit me, because my commits have the following structure

fix: fix incorrect behaviour

META: task/2, bug/3

as MR can close a bug or several tasks in parallel. Due to the fact that I didn’t find the Description field with Gitlab MR (there is only Name). I was forced to use the script below to get all the related entities with MR. it’s fragile and redundant, but it works. Could you please advise if there is any other way to accomplish this task?

what’s interesting:

  1. Is there any way to set global variables? I used another database for storage and reading
    const token = await fibery.getEntityById('Runa/Env', 'd27260a0-e9f3-11ee-9f13-81b4ace4cd35', ['Value'])
  2. How do I get the MP description otherwise? I only got it by querying gitlab.
const fibery = context.getService('fibery');
const syncIdRegex = /^.*?_(.*?)_(.*?)_/;
const metaRegex = /META:\s*([^\n]+)/
const mrIdRegex = /(\d+)(?!.*\d)/
const entityRegex = /(\w+)\/(\d+)/;

for (const entity of args.currentEntities) {
    const { ['Sync Id']: syncId, ['GitLab Link']: gitlabLink } = entity

    const projectId = syncId.match(syncIdRegex)[1];
    const mrId = gitlabLink.match(mrIdRegex)[1]

    const http = context.getService('http');

    const token = await fibery.getEntityById('Runa/Env', 'd27260a0-e9f3-11ee-9f13-81b4ace4cd35', ['Value'])

    const { description } = JSON.parse(
        await http.getAsync(`https://gitlab.com/api/v4/projects/${projectId}/merge_requests/${mrId}`, {
            headers: {
                'Content-type': 'application/json',
                'PRIVATE-TOKEN': token.Value
            }
        })
    );

    if (metaRegex.test(description)) {
        const meta = description.match(metaRegex)[1]

        const items = meta.split(/,\s*/)

        for (const item of items) {
            const [_, type, id] = item.match(entityRegex);

            switch (type) {
                case 'task':
                case 'bug':
                    const alias = type[0].toUpperCase() + type.slice(1)
                    const [related] = await fibery.executeSingleCommand({
                        "command": "fibery.entity/query",
                        "args": {
                            "query": {
                                "q/from": `Runa/${alias}`,
                                "q/select": [
                                    'fibery/id',
                                    'fibery/public-id',
                                ],
                                'q/where': ['=', 'fibery/public-id', "$id"],
                                "q/limit": 1
                            },
                            "params": {
                                "$id": id,
                            }
                        }
                    });

                    if (related) {
                        await fibery.addCollectionItem(entity.type, entity.id, `${alias}s`, related['fibery/id']);
                    }

                    break;
                default:
                    console.log(`Sorry, incorrect entity type - ${type}.`);
            }
        }
    }
}

The Description field for Gitlab merge requests should be included in the sync.

And given that it is, you can probably achieve what you need by using a ReplaceRegex([Description (Snippet)],....) function to extract the task and bug ids (assuming that the description does not exceed the limit for what’s included in a snippet (1000 chars).

@Chr1sG Thanks for the reply, I have a few questions.

My commit contains the following description

fix: fix incorrect behaviour

META: task/2, bug/3

and yes, I see body part inside MR entity

but his presentation is very strange, maybe I’m doing something wrong.

const fibery = context.getService('fibery');

for (const entity of args.currentEntities) {
    const ent = await fibery.getEntityById(entity.type, entity.id, ['Description']);

    console.log(ent)
}

return

{
  Id: '11db5b76-edb5-41bc-8525-03a228ebedce',
  Description: {
    Secret: 'b060dacb-1653-4d04-b4c1-0a133e6e4033',
    Id: '7068bdfe-8c72-402b-bd28-3be29835a678'
  }
}

I didn’t understand the context in which to apply this, could you give an example or link to the documentation

Thank you.

I meant that you can create a formula field to extract the task id, and a separate one to extract the bug id.
(no need to use scripts)

But having said that, it still is challenging to autolink if you need to link to multiple tasks (or multiple bugs) :confused:

@Chr1sG why such a strange representation of the blockDescription? If it was in understandable text, I wouldn’t need to API to gitlab

{
  Id: '11db5b76-edb5-41bc-8525-03a228ebedce',
  Description: {
    Secret: 'b060dacb-1653-4d04-b4c1-0a133e6e4033',
    Id: '7068bdfe-8c72-402b-bd28-3be29835a678'
  }
}

Because rich text fields are not like other fields.

Basically, you need to use the getDocumentContent function.
See here.

@Chr1sG thanks for the tip with getDocumentContent it simplified the script

const fibery = context.getService('fibery');

const metaRegex = /META:\s*((?:\w+\/\d+(?:,\s*)?)+)/g
const entitiesRegex = /\w+\/\d+/g;

for (const entity of args.currentEntities) {
    const { ['Description']: { secret } } = await fibery.getEntityById(entity.type, entity.id, ['Description']);

    const description = await fibery.getDocumentContent(secret)

    if (metaRegex.test(description)) {
        const [meta] = description.match(metaRegex)

        const items = meta.match(entitiesRegex)

        for (const item of items) {
            const [type, id] = item.split("/");

            switch (type) {
                case 'task':
                case 'bug':
                    const alias = type[0].toUpperCase() + type.slice(1)
                    const [related] = await fibery.executeSingleCommand({
                        "command": "fibery.entity/query",
                        "args": {
                            "query": {
                                "q/from": `Runa/${alias}`,
                                "q/select": [
                                    'fibery/id',
                                    'fibery/public-id',
                                ],
                                'q/where': ['=', 'fibery/public-id', "$id"],
                                "q/limit": 1
                            },
                            "params": {
                                "$id": id,
                            }
                        }
                    });

                    if (related) {
                        await fibery.addCollectionItem(entity.type, entity.id, `${alias}s`, related['fibery/id']);
                    }

                    break;
                default:
                    console.log(`Sorry, incorrect entity type - ${type}.`);
            }
        }
    }
}

Looks good.

FWIW you might find using a graphQl query a bit simpler/easier than using the executeSingleCommand method.

To be honest, I didn’t understand how to use graphql in the context of automation, the examples in the documentation didn’t make it clear. maybe I was looking in the wrong place

You can replace the executeSingleCommand with something like this instead:

const response = await fibery.graphql('Runa', 'query getBug($searchId:String){findBugs(publicId:{is:$searchId}){id}}',{'searchId':id});

You will get a response like this:

{
  data: {
    findBugs: [ { id: '7034d130-2984-4c9f-bdaa-390a3b2f60f4' } ]
  }
}

which you can then easily use later in your addCollectionItem function call:

const bug = response.data.findBugs[0].id

Yep, looks cleaner, but, as unterstand, I need implement branch for Task, Bug, etc entity

And also what is getBug? Is predefined function fo entity?

Thank you for your responses

Yeah, it is db specific, but addCollectionItem is db-specific too, so no real difference performance wise. It would need maintaining if you think the types are likely to change over time, but that scenario would also require you to update your script to specify them.

No, it’s just a query name, you could choose anything.