Template Content
About the template
About ScriptRunner Connect
What is ScriptRunner Connect?
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.
This template migrates Tempo Cloud attributes from one Cloud instance to another. There are 2 ways to perform migration:
Following scripts are included:
MigrateAttributes
- Run this script to start the migration process.Setup source and target Tempo Cloud connectors.
When you run the migration script, following will happen:
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.
Bunch of information is outputted to the console while the integration is running.
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.
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;
}