Migrate Tempo Cloud attributes from one instance to another


Get Started

Not the template you're looking for? Browse more.

About the template


This integration migrates Tempo attributes from one Cloud instance to another. Get started to learn more.

About ScriptRunner Connect


What is ScriptRunner Connect?

ScriptRunner Connect is an AI assisted code-first (JavaScript/TypeScript) integration platform (iPaaS) for building complex integrations and automations.

Can I try it out for free?

Yes. ScriptRunner Connect comes with a forever free tier.

Can I customize the integration logic?

Absolutely. The main value proposition of ScriptRunner Connect is that you'll get full access to the code that is powering the integration, which means you can make any changes to the the integration logic yourself.

Can I change the integration to communicate with additional apps?

Yes. Since ScriptRunner Connect specializes in enabling complex integrations, you can easily change the integration logic to connect to as many additional apps as you need, no limitations.

What if I don't feel comfortable making changes to the code?

First you can try out our AI assistant which can help you understand what the code does, and also help you make changes to the code. Alternatively you can hire our professionals to make the changes you need or build new integrations from scratch.

Do I have to host it myself?

No. ScriptRunner Connect is a fully managed SaaS (Software-as-a-Service) product.

What about security?

ScriptRunner Connect is ISO 27001 and SOC 2 certified. Learn more about our security.

Template Content


README

Scripts

TypeScriptMigrateAttributes

README


Overview

This template migrates Tempo Cloud attributes from one Cloud instance to another. There are 2 ways to perform migration:

  • Full migration will remove all attributes from target instance prior the migration;
  • Exclusive migration will skip migration for attributes with the same key that already exist in target instance.

Following scripts are included:

  • MigrateAttributes - Run this script to start the migration process.

Workspace setup

Setup source and target Tempo Cloud connectors.

Migration logic

When you run the migration script, following will happen:

  • It will retrieve attributes from source instance;
  • Depending on chosen mode, attributes will be copied to target instance.

Running the migration

In Parameters you'll see DELETE_TARGET_ATTRIBUTES variable, value of which you can change to choose the mode of migration.

Then, run the MigrateAttributes script manually. Once the migration process is underway, you can abort at any time, by clicking on the Abort Invocation button for the message that you received in the console when you triggered your migration script. Do not clear the console, otherwise you will lose the option to abort the process.

Keeping an eye on the migration progress

Bunch of information is outputted to the console while the integration is running.

Re-trying failed attributes

If an error occured during full migration process, you can change DELETE_TARGET_ATTRIBUTES in Parameters to false and run the script again so it will migrate the rest of the attributes that didn't migrate the first time.

API Connections


./api/tempo/cloud/source@managed-api/tempo-cloud-v4-sr-connect
TypeScriptMigrateAttributes

import { WorkAttributeAsResponse } from "@managed-api/tempo-cloud-v4-core/definitions/WorkAttributeAsResponse";
import TempoCloudSource from "./api/tempo/cloud/source";
import TempoCloudTarget from "./api/tempo/cloud/target";
import { throttleAll } from 'promise-throttle-all';

export default async function (event: any, context: Context): Promise<void> {
    // Set global error strategies to retry rate limited requests infinitely
    TempoCloudSource.setGlobalErrorStrategy(builder => builder.retryOnRateLimiting(-1, 1000));
    TempoCloudTarget.setGlobalErrorStrategy(builder => builder.retryOnRateLimiting(-1, 1000));

    const { DELETE_TARGET_ATTRIBUTES, TEMPO_API_CONCURRENCY } = getEnvVars(context);

    console.log('Retrieving attributes from the source instance...');
    const sourceAttributes = await retrieveAttributes(TempoCloudSource);

    if (!sourceAttributes.length) {
        console.log('No attributes found from source instance.');
        return;
    }

    console.log(`Retrieved ${sourceAttributes.length} attributes from source instance.`);
    const transformedAttributes = transformAttributes(sourceAttributes);

    if (DELETE_TARGET_ATTRIBUTES) {
        console.log('Retrieving attributes from the target instance...');
        const attributes = await retrieveAttributes(TempoCloudTarget);
        console.log(`Retrieved ${attributes.length} attributes from target instance.`);

        try {
            console.log('Deleting attributes from target instance...');
            await throttleAll(TEMPO_API_CONCURRENCY, attributes.map(attr => () => TempoCloudTarget.Work.Attribute.deleteAttribute({ key: attr.key })));
            console.log('All attributes deleted from target instance.');
        } catch (e) {
            console.error('Failed to delete existing attributes in target instance', e);
            return;
        }

        console.log('Starting migration...');
        await performMigration(transformedAttributes, true, TEMPO_API_CONCURRENCY);
    } else {
        console.log('Starting migration...');
        await performMigration(transformedAttributes, false, TEMPO_API_CONCURRENCY);
    }
}

async function retrieveAttributes(instance: typeof TempoCloudSource, attributes: WorkAttributeAsResponse[] = []): Promise<WorkAttributeAsResponse[]> {
    const response = await instance.Work.Attribute.getAttributes({
        offset: attributes.length,
    });

    const currentAttributesCount = attributes.length;

    if (response.results.length) {
        response.results.forEach(attr => {
            if (!attributes.some(a => a.key === attr.key)) {
                attributes.push(attr);
            }
        });
    }

    if (currentAttributesCount < attributes.length) {
        await retrieveAttributes(instance, attributes);
    }

    return attributes;
}

async function performMigration(attributes: Attribute[], fullMigration: boolean, concurrency: number): Promise<void> {
    try {
        const migratedAttributes = (await throttleAll(concurrency, attributes.map(a => () => copyAttribute(a, fullMigration)))).filter(a => a === true);
        console.log(`Migration complete! Attributes migrated: ${migratedAttributes.length}`);
    } catch (err) {
        console.error('Failed to perform migration of the attributes', err);
    }
}

async function copyAttribute(attribute: Attribute, skipExisting = true): Promise<boolean> {
    try {
        if (!skipExisting) {
            const existingAttribute = await TempoCloudTarget.Work.Attribute.getAttribute<null>({
                key: attribute.key,
                errorStrategy: {
                    handleHttp404Error: () => null
                }
            });

            if (existingAttribute) {
                return false;
            }
        }

        await TempoCloudTarget.Work.Attribute.createAttribute({
            body: attribute,
        });

        return true;
    } catch (e) {
        console.error('Failed to copy attribute', e, attribute);
    }

    return false;
}

export function transformAttributes(attributes: WorkAttributeAsResponse[]) {
    return attributes.map((attr) => ({
        name: attr.name,
        key: attr.key,
        type: attr.type,
        required: attr.required,
        values: Object.values(attr.names ?? {})
    }));
}

export interface Attribute {
    name: string;
    key: string;
    type: "ACCOUNT" | "CHECKBOX" | "INPUT_FIELD" | "INPUT_NUMERIC" | "STATIC_LIST";
    required: boolean;
    values?: string[];
}

interface EnvVars {
    DELETE_TARGET_ATTRIBUTES: boolean;
    TEMPO_API_CONCURRENCY: number;
}

export function getEnvVars(context: Context) {
    return context.environment.vars as EnvVars;
}
Documentation · Support · Suggestions & feature requests