I have created a Forge app with a custom field that is read-only. Currently, it displays the issue creation date fetched from the Jira API. Now, I want to make this field configurable so that users can choose how they want to see the result—whether in days, hours, or minutes. How can I implement this configurability in my Forge app?
Here is my menifest file : modules:
jira:customField:
- key: custom-field-hello-world
name: custom-field-a
description: A hello world custom field.
type: string
validation:
expression: value == null || !!value.match(“[1]+$”)
errorMessage: The value must consist only of letters
edit:
resource: main
contextConfig:
function: timeSinceCreatedConfig
- key: editable-custom-field
name: editable-custom-field
description: A editable custom field custom field.
type: string
validation:
expression: value == null || !!value.match("^[A-Za-z]+$")
errorMessage: The value must consist only of letters
- key: read-only-custom-field
name: New-Read-Only-Custom-Field
description: A Read Only custom field.
type: string
# take note that we are using value.function here
value:
function: getAbhi
readOnly: true
- key: time-since-created
name: time-since-created
description: A time since created custom field.
type: string
value:
function: getTime
readOnly: true
contextConfig:
function: timeSinceCreatedConfig
function:
- key: getAbhi
handler: index.getAbhi
- key: getTime
handler: index.getTime
- key: timeSinceCreatedConfig
handler: index.timeSinceCreatedConfig
resources:
- key: main
path: static/hello-world/build # Ensure this path is correct
permissions:
content:
styles:
- unsafe-inline
scopes:
- read:jira-work
- write:jira-work
app:
runtime:
name: nodejs22.x
and here is my index.js file : import api, { route } from “@forge/api”;
import { timeSinceCreatedConfig } from “./config”; // Import the config function
export { timeSinceCreatedConfig }; // Export it so Forge can use it
// You need to export it so it can be referenced by the manifest
export const getAbhi = (args) => {
// console.log(“Function getAbhi was called”, args);
// Assign the value to the returned issue/s
return args.issues.map(issue => ‘Hello Abhi’);
}
// export async function getTime(event) {
// // const issueIdOrKey = event.context.issueKey;
// const issueIdOrKey = event.issues[0].id;
// console.log(‘issueIdOrKey’, issueIdOrKey)
// const response = await api.asApp().requestJira(route/rest/api/3/issue/${issueIdOrKey}
, {
// headers: {
// ‘Accept’: ‘application/json’
// }
// });
// // console.log(‘response’, response)
// const data = await response.json();
// // console.log(‘Data’,data)
// const createdDate = data.fields.created; // Created date from Jira
// console.log(‘createdDate’, createdDate)
// if (!createdDate) {
// return { value: “Unknown” };
// }
// const createdTime = new Date(createdDate);
// console.log(‘createdTime’,createdTime)
// const now = new Date();
// const diffMs = now - createdTime;
// // Convert milliseconds to a human-readable format
// const minutes = Math.floor(diffMs / (1000 * 60));
// const hours = Math.floor(minutes / 60);
// const days = Math.floor(hours / 24);
// let timeSinceCreated;
// if (days > 0) {
// timeSinceCreated = ${days} day${days > 1 ? "s" : ""} ago
;
// } else if (hours > 0) {
// timeSinceCreated = ${hours} hour${hours > 1 ? "s" : ""} ago
;
// } else {
// timeSinceCreated = ${minutes} minute${minutes > 1 ? "s" : ""} ago
;
// }
// console.log(‘New time’,timeSinceCreated)
// console.log(‘New time’,event.issues[0].value)
// // return { value: createdDate };
// return event.issues.map(issue => timeSinceCreated);
// //return event.issues[0].value;
// // return createdDate;
// }
// export async function getTime(event) {
// console.log(‘hehe’,event)
// const issueIdOrKey = event.issues[0].id;
// // const response = await api.asApp().requestJira(route/rest/api/3/issue/${issueIdOrKey}
, {
// // headers: { ‘Accept’: ‘application/json’ }
// // });
// const response = await api.asApp().requestJira(route/rest/api/3/issue/${issueIdOrKey}
, {
// headers: {
// ‘Accept’: ‘application/json’
// }
// });
// const data = await response.json();
// const createdDate = data.fields.created;
// console.log(‘createdDate’,createdDate)
// if (!createdDate) {
// return { value: “Unknown” };
// }
// const createdTime = new Date(createdDate);
// const now = new Date();
// const diffMs = now - createdTime;
// // Get user-selected format
// // const format = event.config?.format || “days”;
// const format = event.fieldConfig?.format || “days”;
// console.log(‘format’,format)
// let timeSinceCreated;
// if (format === “days”) {
// const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
// timeSinceCreated = ${days} day${days !== 1 ? "s" : ""} ago
;
// } else if (format === “hours”) {
// const hours = Math.floor(diffMs / (1000 * 60 * 60));
// timeSinceCreated = ${hours} hour${hours !== 1 ? "s" : ""} ago
;
// } else {
// const minutes = Math.floor(diffMs / (1000 * 60));
// timeSinceCreated = ${minutes} minute${minutes !== 1 ? "s" : ""} ago
;
// }
// return event.issues.map(issue => timeSinceCreated);
// }
export async function getTime(event) {
try {
console.log(“Event received:”, event);
// Ensure issues exist
if (!event.issues || event.issues.length === 0) {
console.error("No issues provided.");
return [];
}
const issueIdOrKey = event.issues[0].id;
const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueIdOrKey}`, {
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
console.error("Jira API request failed:", response.status, await response.text());
return event.issues.map(() => "Error fetching issue");
}
const data = await response.json();
const createdDate = data.fields.created;
if (!createdDate) {
console.error("Issue has no created date.");
return event.issues.map(() => "Unknown");
}
// Calculate time difference
const createdTime = new Date(createdDate);
const now = new Date();
const diffMs = now - createdTime;
// Fetch config
const format = event.fieldConfig?.format || "days";
console.log("Using format:", format);
let timeSinceCreated;
if (format === "days") {
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
timeSinceCreated = `${days} day${days !== 1 ? "s" : ""} ago`;
} else if (format === "hours") {
const hours = Math.floor(diffMs / (1000 * 60 * 60));
timeSinceCreated = `${hours} hour${hours !== 1 ? "s" : ""} ago`;
} else {
const minutes = Math.floor(diffMs / (1000 * 60));
timeSinceCreated = `${minutes} minute${minutes !== 1 ? "s" : ""} ago`;
}
console.log("Final time value:", timeSinceCreated);
// Return array for each issue
return event.issues.map(() => timeSinceCreated);
} catch (error) {
console.error(“Error in getTime function:”, error);
return event.issues.map(() => “Error”);
}
}
A-Za-z ↩︎