Initial commit: Webex CC + Jira + xAI service

Core capabilities:
- Jira ticket lifecycle: status, update, comment, transitions, close
- JSM Store Support request creation with Assets object resolution
- Assets AQL diagnostic probe endpoint with schema/type introspection
- Webex transcript ingestion (audio + JSON + human-readable) with
  restricted-visibility summary comments
- Grok-powered single-ticket and open-tickets-by-reporter summaries

Repo hygiene:
- .gitignore covering .env, node_modules, logs, IDE dirs
- .env.example documenting every env var
- discover-ss-*.js scripts refactored to read credentials from .env
- README covering setup, endpoints, and the Assets scope-vs-role gotcha

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jmcqueen 2026-07-01 15:23:20 -04:00
commit 1070967870
34 changed files with 7102 additions and 0 deletions

17
.dockerignore Normal file
View file

@ -0,0 +1,17 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.gitattributes
README.md
.env
.env.*
!.env.example
logs
*.log
.continue
.vscode
.idea
*.md

50
.env.example Normal file
View file

@ -0,0 +1,50 @@
# =============================================
# .env.example
# Copy to .env and fill in real values. DO NOT commit .env.
# =============================================
# --- Server ---
PORT=1866
NODE_ENV=development
# --- Jira ---
# Preferred: use the JIRA_CLOUD_ID gateway form. When set, requests go to
# https://api.atlassian.com/ex/jira/{cloudId}. If unset, JIRA_BASE_URL is used
# directly (e.g. https://your-site.atlassian.net).
JIRA_CLOUD_ID=
JIRA_BASE_URL=https://your-site.atlassian.net
# Auth. 'basic' = email + API token (Atlassian API tokens).
# 'bearer' = OAuth bearer token in Authorization header.
JIRA_AUTH_TYPE=basic
JIRA_EMAIL=service-account@example.com
JIRA_API_TOKEN=REPLACE_ME
# JSM Service Desk (Store Support). Numeric service desk id.
JIRA_SERVICE_DESK_ID=170
# Role name used to restrict visibility on comments posted by this service.
# Common values: "Administrators", "Service Desk Team".
JIRA_COMMENT_VISIBILITY_ROLE=Service Desk Team
# --- Jira Assets (Store Number -> Assets object resolution) ---
# Workspace id for Jira Assets. If unset, the app tries to auto-discover via
# /rest/servicedeskapi/assets/workspace, but setting it explicitly is safer
# on tenants with more than one Assets workspace.
JIRA_ASSETS_WORKSPACE_ID=
# Numeric object schema and object type id for the Stores schema in Assets.
JIRA_ASSETS_STORE_SCHEMA_ID=68
JIRA_ASSETS_STORE_OBJECT_TYPE_ID=109
# The attribute name (as shown in the Assets UI) holding the store number.
JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE=Store Number
# Optional. If set, the app will also try attribute[<id>]=... form in AQL.
JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE_ID=
# The custom field on the JSM request that holds the Store Assets reference.
JIRA_STORE_CUSTOM_FIELD_ID=customfield_10261
# --- xAI (Grok) ---
XAI_API_KEY=REPLACE_ME
XAI_BASE_URL=https://api.x.ai/v1

54
.gitignore vendored Normal file
View file

@ -0,0 +1,54 @@
# =============================================
# Secrets — NEVER commit these
# =============================================
.env
.env.*
!.env.example
*.pem
*.key
*.p12
*.pfx
credentials*
secrets*
# =============================================
# Node
# =============================================
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.npm/
.yarn/
.pnp.*
# =============================================
# Runtime / build artifacts
# =============================================
logs/
*.log
dist/
build/
coverage/
.nyc_output/
tmp/
.tmp/
# =============================================
# Editor / IDE / OS
# =============================================
.vscode/
.idea/
.continue/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
Desktop.ini
# =============================================
# Docker
# =============================================
docker-compose.override.yml

58
Dockerfile Normal file
View file

@ -0,0 +1,58 @@
# syntax=docker/dockerfile:1
# =============================================
# Stage 1: Builder (install deps + prepare app)
# =============================================
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files first for better layer caching
COPY package*.json ./
# Install all dependencies (including dev if needed)
RUN npm ci --ignore-scripts
# Copy source code
COPY . .
# Optional: If you ever add a build step (e.g. TypeScript), run it here
# RUN npm run build
# =============================================
# Stage 2: Production (lean runtime image)
# =============================================
FROM node:22-alpine AS production
# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 -G nodejs nodejs
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install ONLY production dependencies
RUN npm ci --only=production --ignore-scripts && \
npm cache clean --force
# Copy built/ready files from builder stage
COPY --from=builder /app/src ./src
COPY --from=builder /app/node_modules ./node_modules
# Change ownership to non-root user
RUN chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose the port your app listens on (from your config)
EXPOSE 1866
# Detailed healthcheck using the new /health endpoint
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:1866/health || exit 1
# Start the app directly with node (better than npm start in containers)
CMD ["node", "src/app.js"]

84
README.md Normal file
View file

@ -0,0 +1,84 @@
# wxcc-ai
Node/Express service that wires Webex Contact Center summaries, Jira tickets (core + JSM Store Support), and xAI Grok summarization into a single set of internal HTTP endpoints.
## Setup
```bash
npm install
cp .env.example .env
# fill in JIRA_*, XAI_*, and optionally the Assets values
npm run dev
```
The server listens on `PORT` (default `1866`).
## Environment variables
See [`.env.example`](./.env.example) for the full list. The important ones:
| Var | Purpose |
| --- | --- |
| `JIRA_CLOUD_ID` | If set, requests use `https://api.atlassian.com/ex/jira/{cloudId}`. |
| `JIRA_BASE_URL` | Fallback for legacy site URLs (`https://your-site.atlassian.net`). |
| `JIRA_EMAIL`, `JIRA_API_TOKEN` | Basic auth to Jira. `JIRA_AUTH_TYPE=bearer` switches to bearer. |
| `JIRA_SERVICE_DESK_ID` | Numeric JSM service desk id for Store Support. |
| `JIRA_ASSETS_WORKSPACE_ID` | Assets workspace id (auto-discovered if omitted). |
| `JIRA_ASSETS_STORE_SCHEMA_ID`, `_OBJECT_TYPE_ID` | Locate the Store schema/type. |
| `JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE[_ID]` | Attribute name (or id) holding the store number in Assets. |
| `JIRA_STORE_CUSTOM_FIELD_ID` | Custom field on the JSM request that holds the Store Assets reference. |
| `XAI_API_KEY`, `XAI_BASE_URL` | Grok credentials for summary generation. |
## Endpoints
Base path: `/api/wxccai`.
### Read
- `GET /getticket?jiraKey=CS-1234` — Grok-summarized single ticket.
- `GET /open-tickets-by-reporter?email=user@example.com` — Grok-summarized list of open tickets a person reported.
- `GET /ticket/:key/status` — raw status fields (no Grok).
- `GET /ticket/:key/transitions` — available workflow transitions.
### Write
- `PATCH /ticket/:key` — body: `{ summary?, description?, priority?, labels?, assigneeAccountId?, additional? }`
- `POST /ticket/:key/comment` — body: `{ text, internal? }`
- `POST /ticket/:key/close` — body: `{ transitionName?, resolution?, comment?, internal? }` (auto-picks the first "done" transition when `transitionName` omitted).
### Store Support (JSM requests)
- `GET /ssRequestTypes` — supported `subType` values + Assets config summary.
- `POST /createSSRequest` — body includes `subType`, `summary`, `storeNumber` (auto-resolved via Assets), `onBehalfOf`, `description`, `additional`.
### Webex webhook
- `POST /issueTranscript/:jiraKey` — attaches audio + JSON transcript + human-readable transcript, then posts a restricted-visibility summary comment.
### Debug (non-production only)
- `GET /debug/assetsProbe?storeNumber=305` — runs several AQL variants against Jira Assets and returns visible schemas + object-type detail + a computed diagnosis. Returns 404 when `NODE_ENV=production`.
## Jira Assets gotcha
`createSSRequest` resolves a store number to an Assets object reference (`customfield_10261`). Two independent permission layers must both grant access, or every AQL query silently returns `total: 0`:
1. **OAuth scopes** on the API token: `read:cmdb-schema:jira`, `read:cmdb-type:jira`, `read:cmdb-object:jira`, `read:cmdb-attribute:jira` (and the `write:` equivalents for updates).
2. **Object Schema role membership** in Jira Assets itself — the underlying user needs to be added to a role on the Store schema in Jira → Assets → Object schemas → Configure → Roles.
If AQL keeps returning `total: 0` with HTTP 200, run the probe endpoint above; the `diagnosis` field will tell you exactly which layer is missing.
## Docker
```bash
npm run docker:build
npm run docker:run
```
The image runs as a non-root user and exposes `1866`. `HEALTHCHECK` pings `GET /health`.
## Repo hygiene
- Secrets live only in `.env`, which is `.gitignore`d.
- The two `discover-ss-*.js` helper scripts read from `.env` — never hardcode credentials in them.
- The debug logger no longer echoes the outbound `Authorization` header. Rotate any token that appears in older `logs/*.log` files.

51
discover-ss-fields.js Normal file
View file

@ -0,0 +1,51 @@
// discover-ss-fields.js
// One-off helper to dump JSM request-type field definitions for the Store
// Support service desk. Output files (ss-fields-<id>.json) are used to keep
// REQUEST_TYPE_MAP in src/services/jiraService.js in sync with what JSM
// actually accepts.
//
// Usage: node discover-ss-fields.js
// Reads credentials from .env — never commit real tokens into this file.
import 'dotenv/config';
import fetch from 'node-fetch';
import fs from 'fs/promises';
const BASE_URL = (process.env.JIRA_BASE_URL || 'https://your-site.atlassian.net').replace(/\/$/, '');
const SERVICE_DESK_ID = process.env.JIRA_SERVICE_DESK_ID || '170';
const EMAIL = process.env.JIRA_EMAIL;
const TOKEN = process.env.JIRA_API_TOKEN;
if (!EMAIL || !TOKEN) {
console.error('JIRA_EMAIL and JIRA_API_TOKEN must be set in .env');
process.exit(1);
}
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from(`${EMAIL}:${TOKEN}`).toString('base64')
};
// Request-type ids that we care about (matches REQUEST_TYPE_MAP in jiraService.js).
const importantTypes = [269, 266, 273, 274, 267, 275, 272, 268, 271, 270, 426, 493];
async function getFields(requestTypeId) {
console.log(`Fetching fields for request type ${requestTypeId}...`);
const res = await fetch(`${BASE_URL}/rest/servicedeskapi/servicedesk/${SERVICE_DESK_ID}/requesttype/${requestTypeId}/field`, { headers });
const data = await res.json();
if (res.ok) {
await fs.writeFile(`ss-fields-${requestTypeId}.json`, JSON.stringify(data, null, 2));
console.log(` saved ss-fields-${requestTypeId}.json`);
} else {
console.error(' error:', data);
}
return data;
}
for (const id of importantTypes) {
await getFields(id);
}
console.log('\nDone. Check the generated ss-fields-*.json files.');

View file

@ -0,0 +1,80 @@
// discover-ss-request-types.js
// One-off helper to list all JSM request types on the Store Support service
// desk. Produces ss-request-types.json (raw) and ss-request-types-clean.json
// (id/name/issueTypeId only) — both useful for maintaining REQUEST_TYPE_MAP
// in src/services/jiraService.js.
//
// Usage: node discover-ss-request-types.js
// Reads credentials from .env — never commit real tokens into this file.
import 'dotenv/config';
import fetch from 'node-fetch';
import fs from 'fs/promises';
const BASE_URL = (process.env.JIRA_BASE_URL || 'https://your-site.atlassian.net').replace(/\/$/, '');
const SERVICE_DESK_ID = process.env.JIRA_SERVICE_DESK_ID || '170';
const EMAIL = process.env.JIRA_EMAIL;
const TOKEN = process.env.JIRA_API_TOKEN;
if (!EMAIL || !TOKEN) {
console.error('JIRA_EMAIL and JIRA_API_TOKEN must be set in .env');
process.exit(1);
}
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from(`${EMAIL}:${TOKEN}`).toString('base64')
};
async function getRequestTypes() {
console.log(`Fetching request types for service desk ${SERVICE_DESK_ID}...\n`);
const res = await fetch(`${BASE_URL}/rest/servicedeskapi/servicedesk/${SERVICE_DESK_ID}/requesttype?limit=100`, { headers });
const data = await res.json();
if (!res.ok) {
console.error('Error:', data);
return;
}
console.log(`Found ${data.values.length} request types.\n`);
await fs.writeFile('ss-request-types.json', JSON.stringify(data, null, 2));
console.log('Full list saved to ss-request-types.json');
const mapping = data.values.map(rt => ({
id: rt.id,
name: rt.name,
defaultName: rt.defaultName,
description: rt.description,
issueTypeId: rt.issueTypeId,
groupIds: rt.groupIds,
canCreateRequest: rt.canCreateRequest
}));
await fs.writeFile('ss-request-types-clean.json', JSON.stringify(mapping, null, 2));
console.log('Clean mapping saved to ss-request-types-clean.json');
}
async function getFieldsForRequestType(requestTypeId) {
console.log(`\nFetching fields for request type ${requestTypeId}...`);
const res = await fetch(`${BASE_URL}/rest/servicedeskapi/servicedesk/${SERVICE_DESK_ID}/requesttype/${requestTypeId}/field`, { headers });
const data = await res.json();
if (!res.ok) {
console.error('Error:', data);
return null;
}
await fs.writeFile(`fields-requesttype-${requestTypeId}.json`, JSON.stringify(data, null, 2));
console.log(`Fields saved to fields-requesttype-${requestTypeId}.json`);
return data;
}
await getRequestTypes();
// Fetch fields for specific request types by uncommenting below.
// await getFieldsForRequestType(269); // Register Not Functioning Properly
// await getFieldsForRequestType(266); // Broken Device / Hardware
// await getFieldsForRequestType(426); // UKG Pro / Workforce Management Issues

15
docker-compose.yml Normal file
View file

@ -0,0 +1,15 @@
services:
wxcc-jira-grok:
build: .
ports:
- "1866:1866"
env_file:
- .env
restart: unless-stopped
# Optional: healthcheck at compose level
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:1866/health"]
interval: 30s
timeout: 8s
start_period: 15s
retries: 3

1872
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "wxcc-ai",
"version": "1.0.0",
"description": "Webex Contact Center Jira summarizer powered by xAI Grok",
"main": "src/app.js",
"type": "module",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"docker:build": "docker build -t wxcc-jira-grok:latest .",
"docker:run": "docker run -p 1866:1866 --env-file .env --rm wxcc-jira-grok:latest"
},
"dependencies": {
"axios": "^1.8.0",
"axios-retry": "^4.5.0",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"form-data": "^4.0.5",
"node-fetch": "^3.3.2",
"openai": "^4.85.0",
"winston": "^3.17.0"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}

134
src/app.js Normal file
View file

@ -0,0 +1,134 @@
import express from 'express';
import config from './config/index.js';
import wxccRoutes from './routes/wxccRoutes.js';
import { getDetailedHealth } from './services/healthService.js';
import logger from './utilities/logger.js';
const app = express();
// Early request logging (BEFORE body parsers).
// This guarantees we see evidence of requests reaching the app (even if JSON
// parsing fails, wrong method, or 404). Critical for debugging webhooks from
// external systems like Webex CC where the request may never reach our
// route handler.
app.use((req, res, next) => {
const isApi = req.path.startsWith('/api/') || req.url.startsWith('/api/');
const isTranscript = req.path.includes('/issueTranscript') || req.url.includes('/issueTranscript');
if (isApi || isTranscript) {
const logInfo = {
method: req.method,
url: req.originalUrl || req.url,
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
contentType: req.headers['content-type'],
contentLength: req.headers['content-length'],
userAgent: req.headers['user-agent']
};
// Direct console for maximum visibility in docker logs / platform stdout
if (isTranscript) {
console.log('TRANSCRIPT WEBHOOK REQUEST:', JSON.stringify(logInfo));
} else {
console.log('API REQUEST:', JSON.stringify(logInfo));
}
// Also via structured logger (goes to app.log + console per config)
logger.info('Incoming API request', logInfo);
}
next();
});
// Special handling for the Webex transcript webhook.
// The upstream Java client sometimes includes unescaped control characters
// (newlines, etc.) inside the Summaries strings, which breaks strict JSON.parse.
// We use express.raw() for this specific path so we always get the buffer,
// then we sanitize and parse inside the route handler.
app.use('/api/wxccai/issueTranscript', express.raw({
type: 'application/json',
limit: '10mb',
verify: (req, res, buf) => {
req.rawBody = buf; // keep raw for possible future needs
}
}));
// Middleware - normal JSON parser for all other routes
app.use(express.json({
limit: '10mb',
verify: (req, res, buf) => {
req.rawBody = buf; // Store raw buffer for fallback parsing
}
}));
// ========================
// Health Check Routes
// ========================
app.get('/health', async (req, res) => {
try {
const health = await getDetailedHealth();
const statusCode = health.status === 'unhealthy' ? 503 : 200;
res.status(statusCode).json(health);
} catch (err) {
logger.error('Health check failed:', err);
res.status(503).json({
status: 'unhealthy',
message: 'Health check failed',
error: err.message
});
}
});
app.get('/health/detailed', async (req, res) => {
try {
const health = await getDetailedHealth();
res.json(health);
} catch (err) {
res.status(503).json({
status: 'unhealthy',
message: 'Detailed health check failed',
error: err.message
});
}
});
// Simple welcome route at root
app.get('/', (req, res) => {
res.json({
message: "Webex Contact Center + Jira + xAI Grok API service is running",
version: process.env.npm_package_version || "1.0.0",
endpoints: {
health: "/health",
singleTicket: "/api/wxccai/getticket?jiraKey=CS-1234 (Grok-summarized)",
openTicketsByReporter: "/api/wxccai/open-tickets-by-reporter?email=user@example.com",
ticketStatus: "GET /api/wxccai/ticket/:key/status (raw fields, no Grok)",
updateTicket: "PATCH /api/wxccai/ticket/:key body: { summary?, description?, priority?, labels?, assigneeAccountId?, additional? }",
addComment: "POST /api/wxccai/ticket/:key/comment body: { text, internal? }",
transitions: "GET /api/wxccai/ticket/:key/transitions",
closeTicket: "POST /api/wxccai/ticket/:key/close body: { transitionName?, resolution?, comment?, internal? }",
createSSRequest: "POST /api/wxccai/createSSRequest (store support ticket creator; storeNumber auto-resolved via Assets)",
ssRequestTypes: "GET /api/wxccai/ssRequestTypes (lists exact subType values + config notes)",
assetsProbe: "GET /api/wxccai/debug/assetsProbe?storeNumber=305 (non-production only)"
}
});
});
// ========================
// Main API Routes (mounted under /api)
// ========================
app.use('/api', wxccRoutes);
// 404 handler
app.use((req, res) => {
logger.warn('Route not found', {
method: req.method,
url: req.originalUrl || req.url,
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress
});
res.status(404).json({
error: "Route not found",
message: "Available endpoints are under /api/wxccai/..."
});
});
app.listen(config.port, () => {
logger.info(`Server running on port ${config.port} in ${config.nodeEnv} mode`);
});

67
src/config/index.js Normal file
View file

@ -0,0 +1,67 @@
import dotenv from 'dotenv';
dotenv.config();
const cloudId = process.env.JIRA_CLOUD_ID?.trim();
let baseUrl = process.env.JIRA_BASE_URL?.trim();
if (cloudId) {
// When JIRA_CLOUD_ID is provided, use the api.atlassian.com/ex/jira gateway form.
// This is the current style for addressing a specific Jira Cloud site (e.g. for
// /rest/servicedeskapi/... and /rest/api/3/... calls). Falls back to JIRA_BASE_URL
// for legacy site-based URLs like https://your-site.atlassian.net when no cloud ID is set.
baseUrl = `https://api.atlassian.com/ex/jira/${cloudId}`;
} else if (baseUrl) {
// Normalize legacy site base (remove trailing slash)
baseUrl = baseUrl.replace(/\/$/, '');
}
// Validate required environment variables
const requiredEnvVars = [
'JIRA_EMAIL',
'JIRA_API_TOKEN',
'XAI_API_KEY'
];
const missingVars = requiredEnvVars.filter(env => !process.env[env]);
if (missingVars.length > 0) {
console.warn(`Missing required environment variables: ${missingVars.join(', ')}`);
}
export const config = {
port: process.env.PORT || 1866,
nodeEnv: process.env.NODE_ENV || 'development',
jira: {
baseUrl,
cloudId: cloudId || null,
authType: (process.env.JIRA_AUTH_TYPE || 'basic').toLowerCase(), // 'basic' or 'bearer'
email: process.env.JIRA_EMAIL,
apiToken: process.env.JIRA_API_TOKEN,
serviceDeskId: process.env.JIRA_SERVICE_DESK_ID,
// Role used for restricted/internal visibility on summary comments.
// Common values: "Administrators", or a project role you have defined.
commentVisibilityRole: process.env.JIRA_COMMENT_VISIBILITY_ROLE || 'Administrators',
// Assets / CMDB configuration for Store Number object references.
// These are used for the "service object" / Assets object lookup when creating SS tickets.
// The lookup now uses the api.atlassian.com workspace-scoped endpoint:
// https://api.atlassian.com/jsm/assets/workspace/{workspaceId}/v1/object/aql
assetsWorkspaceId: process.env.JIRA_ASSETS_WORKSPACE_ID,
// (optional) numeric schema or object type details
assetsStoreSchemaId: process.env.JIRA_ASSETS_STORE_SCHEMA_ID,
assetsStoreObjectTypeId: process.env.JIRA_ASSETS_STORE_OBJECT_TYPE_ID || '109',
assetsStoreNumberAttribute: process.env.JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE || 'Store Number',
// If you know the attribute ID (e.g. 635 from schema), we can try "attribute[635]" form in AQL
assetsStoreNumberAttributeId: process.env.JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE_ID || null,
// The custom field ID on the request that holds the Store Assets reference
storeCustomFieldId: process.env.JIRA_STORE_CUSTOM_FIELD_ID || 'customfield_10261',
},
xai: {
apiKey: process.env.XAI_API_KEY,
baseUrl: process.env.XAI_BASE_URL || 'https://api.x.ai/v1',
}
};
export default config;

View file

@ -0,0 +1,30 @@
You are assisting a Webex Contact Center agent. Return ONLY a valid JSON array with one object per ticket. No extra text, no markdown, no explanations.
For each ticket, create a short, neutral, spoken-friendly summary (maximum 2 sentences) that includes:
- The initial problem / request from the customer (from the description)
- Key actions that have been taken so far (from description and public comments)
- Current state and any blockers or next steps (based especially on the most recent public notes/comments)
Rules:
- Do NOT mention any names or people
- Be factual and concise — easy to read aloud
- Prioritize the latest public comments to determine current status
Each object must have exactly these fields:
- jiraKey: string
- currentStatus: the exact current status name from Jira (e.g. "In Progress", "Waiting for Customer", "To Do")
- summary: string (the spoken summary described above)
- lastUpdated: ISO timestamp string from the ticket or null
Example:
[
{
"jiraKey": "CS-1234",
"currentStatus": "In Progress",
"summary": "Customer reported slow performance when generating reports. Backend indexing was optimized and a new cache layer was added last week. Final performance testing is now underway.",
"lastUpdated": "2026-04-13T08:38:00-05:00"
}
]
Tickets context:
{{context}}

View file

@ -0,0 +1,30 @@
You are assisting a Webex Contact Center agent. Return ONLY a valid JSON array with one object per ticket. No extra text, no markdown, no explanations.
For each ticket, create a short, neutral, spoken-friendly summary (maximum 2 sentences) that includes:
- The initial problem / request from the customer (from the description)
- Key actions that have been taken so far (from description and public comments)
- Current state and any blockers or next steps (based especially on the most recent public notes/comments)
Rules:
- Do NOT mention any names or people
- Be factual and concise — easy to read aloud
- Prioritize the latest public comments to determine current status
Each object must have exactly these fields:
- jiraKey: string
- currentStatus: the exact current status name from Jira (e.g. "In Progress", "Waiting for Customer", "To Do")
- summary: string (the spoken summary described above)
- lastUpdated: ISO timestamp string from the ticket or null
Example:
[
{
"jiraKey": "CS-1234",
"currentStatus": "In Progress",
"summary": "Customer reported slow performance when generating reports. Backend indexing was optimized and a new cache layer was added last week. Final performance testing is now underway.",
"lastUpdated": "2026-04-13T08:38:00-05:00"
}
]
Tickets context:
{{context}}

574
src/routes/wxccRoutes.js Normal file
View file

@ -0,0 +1,574 @@
import express from 'express';
import axios from 'axios';
import axiosRetry from 'axios-retry';
import { logger, webexLogger } from '../utilities/logger.js';
import * as jiraService from '../services/jiraService.js';
import grokService from '../services/grokService.js';
import config from '../config/index.js';
// Configure retry for transient failures
axiosRetry(axios, {
retries: 3,
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.response?.status === 429 ||
error.response?.status >= 500;
}
});
const router = express.Router();
// ========================
// Single Ticket Lookup
// ========================
router.get('/wxccai/getticket', async (req, res) => {
const jiraKey = req.query.jiraKey?.trim().toUpperCase();
if (!jiraKey || !/^[A-Z0-9]+-\d+$/.test(jiraKey)) {
return res.status(400).json({
jiraKey: jiraKey || "missing",
assignedTo: "Error",
status: "Error",
summary: "Invalid or missing Jira key"
});
}
try {
const [issueData, description, comments] = await Promise.all([
jiraService.fetchJiraIssue(jiraKey).catch(() => ({})),
jiraService.fetchPlainDescription(jiraKey).catch(() => "No description."),
jiraService.fetchPublicComments(jiraKey).catch(() => [])
]);
const fields = issueData.fields || {};
const assignedTo = fields.assignee?.displayName || fields.assignee?.name || "Unassigned";
const status = fields.status?.name || "Unknown";
const title = fields.summary || "No title";
let commentsText = "";
if (Array.isArray(comments) && comments.length > 0) {
const recent = comments.slice(-5);
commentsText = "\nRecent comments:\n" + recent.map(c => {
let dateStr = "unknown-date";
if (typeof c.created === 'string') {
dateStr = c.created.slice(0, 10);
} else if (c.created?.iso8601) {
dateStr = c.created.iso8601.slice(0, 10);
} else if (c.created?.jira) {
dateStr = c.created.jira.slice(0, 10);
}
const bodyPreview = typeof c.body === 'string'
? c.body.substring(0, 250) + (c.body.length > 250 ? '...' : '')
: '[No body]';
return `[${dateStr}] ${c.author || 'Unknown'}: ${bodyPreview}`;
}).join('\n');
}
const context = `
Ticket: ${jiraKey}
Title: ${title}
Status: ${status}
Assigned: ${assignedTo}
Description: ${description}
${commentsText}
`.trim();
const summary = await grokService.generateTicketSummary(context);
res.json({
jiraKey,
assignedTo,
status,
summary
});
} catch (error) {
logger.error(`Ticket lookup failed for ${jiraKey}:`, error);
res.status(200).json({
jiraKey,
assignedTo: "Error",
status: "Lookup failed",
summary: `Unable to fetch ticket details: ${error.message.substring(0, 150)}`
});
}
});
// ========================
// Open Tickets by Reporter
// ========================
router.get('/wxccai/open-tickets-by-reporter', async (req, res) => {
const email = req.query.email?.trim().toLowerCase();
if (!email || !email.includes('@')) {
return res.status(400).json({
error: "Invalid or missing email address",
message: "Please provide a valid email address to search for open tickets"
});
}
try {
const issues = await jiraService.searchOpenTicketsByReporterEmail(email);
const ticketArray = await grokService.generateOpenTicketsSummary(issues);
res.status(200).json(ticketArray);
} catch (error) {
logger.error(`Open tickets by reporter failed for ${email}:`, error);
// Return safe empty response on runtime failures (Jira/Grok down, etc.)
// to match the degrade-gracefully + 200 pattern used by /getticket and the
// webhook endpoint. 400 is still used above for clear input validation errors.
res.status(200).json([]);
}
});
// ========================
// Store Support (SS) Ticket Creation
// ========================
// GET /api/wxccai/ssRequestTypes → list of exact subType strings you can use
// POST /api/wxccai/createSSRequest with body (see comment in handler)
router.get('/wxccai/ssRequestTypes', (req, res) => {
try {
const types = jiraService.getSupportedSSSubTypes ? jiraService.getSupportedSSSubTypes() : [];
res.json({
serviceDeskId: config.jira.serviceDeskId || '170',
subTypes: types,
note: "storeNumber padded to 5 digits. Uses api.atlassian.com workspace-scoped Assets AQL (https://api.atlassian.com/jsm/assets/workspace/{id}/v1/object/aql) because this is a service object.",
assetsConfig: {
objectTypeId: config.jira.assetsStoreObjectTypeId || '109',
attribute: config.jira.assetsStoreNumberAttribute || 'Store Number',
storeCustomFieldId: config.jira.storeCustomFieldId || 'customfield_10261',
workspaceId: config.jira.assetsWorkspaceId || '(auto-discovered via /rest/servicedeskapi/assets/workspace)',
note: 'Always uses https://api.atlassian.com/jsm/assets/workspace/.../v1/object/aql (required for service objects)',
requiredEnv: [
"JIRA_ASSETS_WORKSPACE_ID (recommended; auto-discovered if possible)",
"JIRA_ASSETS_STORE_OBJECT_TYPE_ID (default 109)",
"JIRA_STORE_CUSTOM_FIELD_ID (default customfield_10261)"
]
},
example: {
subType: types[0] || "Register Not functioning properly",
onBehalfOf: "user@ae.com",
summary: "Register 3 is frozen at login screen",
description: "Detailed description (optional)",
storeNumber: "00305", // looked up → [{ "objectId": "82288" }]
additional: {
customfield_10294: "2026-06-18T14:30:00.000+0000"
}
}
});
} catch (e) {
res.status(500).json({ error: 'Failed to list types' });
}
});
// POST body example:
// {
// "subType": "Register Not functioning properly",
// "onBehalfOf": "user@ae.com",
// "summary": "Register 3 is frozen",
// "description": "Customer reports...",
// "storeNumber": "00305", // 5 digits; resolved via api.atlassian.com Assets endpoint to { "objectId": "..." }
// "additional": {
// "customfield_10294": "2026-06-18T14:30:00.000+0000",
// "customfield_10557": "Transaction failed"
// }
// }
// subType must match exactly one of the keys in the REQUEST_TYPE_MAP.
router.post('/wxccai/createSSRequest', async (req, res) => {
const body = req.body || {};
const timestamp = new Date().toISOString();
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
logger.info('Create SS Request received', {
timestamp,
ip,
subType: body.subType,
storeNumber: body.storeNumber,
hasOnBehalfOf: !!body.onBehalfOf
});
if (!body.subType || !body.summary) {
return res.status(400).json({
success: false,
error: 'subType and summary are required'
});
}
try {
const result = await jiraService.createSSRequest(body);
logger.info('Create SS Request succeeded', {
issueKey: result?.issueKey,
subType: body.subType
});
res.status(200).json({
success: true,
issueKey: result?.issueKey,
issueId: result?.issueId,
requestTypeId: result?.requestType?.id,
raw: result // full response for debugging / future use
});
} catch (error) {
logger.error('Create SS Request failed', {
subType: body.subType,
storeNumber: body.storeNumber,
error: error.message,
details: error.details || error.response?.data
});
res.status(400).json({
success: false,
error: error.message,
details: error.details || error.response?.data || null
});
}
});
// ========================
// Ticket lifecycle: status / update / comment / transitions / close
// These are the raw operations (no Grok). Use /wxccai/getticket for the
// Grok-summarized variant.
// ========================
const KEY_RE = /^[A-Z]+-\d+$/;
router.get('/wxccai/ticket/:key/status', async (req, res) => {
const key = req.params.key?.trim().toUpperCase();
if (!key || !KEY_RE.test(key)) {
return res.status(400).json({ error: 'Invalid Jira key' });
}
try {
const status = await jiraService.getTicketStatus(key);
res.json(status);
} catch (err) {
logger.error('status route failed', { key, error: err.message, details: err.details });
res.status(err.status && err.status < 500 ? err.status : 500).json({
error: err.message, details: err.details || null
});
}
});
// Body: { summary?, description?, priority?, labels?, assigneeAccountId?, additional? }
router.patch('/wxccai/ticket/:key', async (req, res) => {
const key = req.params.key?.trim().toUpperCase();
if (!key || !KEY_RE.test(key)) {
return res.status(400).json({ error: 'Invalid Jira key' });
}
try {
const result = await jiraService.updateTicket(key, req.body || {});
res.json({ success: true, ...result });
} catch (err) {
logger.error('update route failed', { key, error: err.message, details: err.details });
res.status(err.status && err.status < 500 ? err.status : 500).json({
success: false, error: err.message, details: err.details || null
});
}
});
// Body: { text: "...", internal?: boolean }
router.post('/wxccai/ticket/:key/comment', async (req, res) => {
const key = req.params.key?.trim().toUpperCase();
if (!key || !KEY_RE.test(key)) {
return res.status(400).json({ error: 'Invalid Jira key' });
}
const { text, internal } = req.body || {};
try {
const result = await jiraService.addComment(key, text, { internal: !!internal });
res.json({ success: true, ...result });
} catch (err) {
logger.error('comment route failed', { key, error: err.message, details: err.details });
res.status(err.status && err.status < 500 ? err.status : 500).json({
success: false, error: err.message, details: err.details || null
});
}
});
router.get('/wxccai/ticket/:key/transitions', async (req, res) => {
const key = req.params.key?.trim().toUpperCase();
if (!key || !KEY_RE.test(key)) {
return res.status(400).json({ error: 'Invalid Jira key' });
}
try {
const transitions = await jiraService.getTransitions(key);
res.json({ key, transitions });
} catch (err) {
logger.error('transitions route failed', { key, error: err.message });
res.status(500).json({ error: err.message });
}
});
// Body: { transitionName?, resolution?, comment?, internal? }
// If transitionName is omitted, we auto-pick the first "done" transition.
router.post('/wxccai/ticket/:key/close', async (req, res) => {
const key = req.params.key?.trim().toUpperCase();
if (!key || !KEY_RE.test(key)) {
return res.status(400).json({ error: 'Invalid Jira key' });
}
const { transitionName, resolution, comment, internal } = req.body || {};
try {
const result = await jiraService.closeTicket(key, {
transitionName,
resolution: resolution || 'Done',
comment,
internal: !!internal
});
res.json({ success: true, ...result });
} catch (err) {
logger.error('close route failed', { key, error: err.message, details: err.details });
res.status(err.status && err.status < 500 ? err.status : 500).json({
success: false, error: err.message, details: err.details || null
});
}
});
// ========================
// Assets diagnostic (non-production only)
// GET /api/wxccai/debug/assetsProbe?storeNumber=305
// Runs several AQL variants against the Assets workspace so we can see which
// query shape actually matches (attribute name vs Name field, padded vs
// unpadded, etc.) and dumps the real attribute names + values from any
// returned objects. This is intentionally verbose; do not expose in prod.
// ========================
router.get('/wxccai/debug/assetsProbe', async (req, res) => {
if ((config.nodeEnv || '').toLowerCase() === 'production') {
return res.status(404).json({ error: 'Not found' });
}
const storeNumber = (req.query.storeNumber ?? '').toString().trim();
try {
const result = await jiraService.probeAssetsForStore(storeNumber);
res.json(result);
} catch (err) {
logger.error('Assets probe failed', { storeNumber, error: err.message });
res.status(500).json({ error: err.message });
}
});
// ========================
// Webex → Jira Transcript + Audio + Summary Comment
// ========================
router.post('/wxccai/issueTranscript/:jiraKey', async (req, res) => {
const jiraKey = req.params.jiraKey?.trim().toUpperCase();
const timestamp = new Date().toISOString();
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
let payload = req.body || {};
// If we hit the raw() parser for this webhook (because of potential bad JSON
// from transcription text), sanitize control characters and parse ourselves.
if (Buffer.isBuffer(req.body)) {
const rawBodyStr = req.body.toString('utf8');
try {
let rawStr = rawBodyStr;
// Trim any leading control characters that would make the top-level JSON invalid.
rawStr = rawStr.replace(/^[\u0000-\u001F]+/, '');
// Only escape control characters that appear *inside* JSON string values.
// Global replacement would turn structural newlines/tabs (the pretty-printing
// in the JSON itself) into literal "\n" text, breaking the top-level structure
// (which is exactly what caused "Expected property name or '}' at position 1").
//
// This regex finds "..." strings (handling escaped inner quotes) and cleans
// only the content inside them. Structural whitespace stays intact so JSON.parse
// still sees valid tokens.
rawStr = rawStr.replace(/"((?:[^"\\]|\\.)*)"/g, (fullMatch, content) => {
const cleaned = content.replace(/[\u0000-\u001F]/g, (ch) => {
switch (ch) {
case '\n': return '\\n';
case '\r': return '\\r';
case '\t': return '\\t';
case '\b': return '\\b';
case '\f': return '\\f';
default:
return '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0');
}
});
return '"' + cleaned + '"';
});
payload = JSON.parse(rawStr);
} catch (parseErr) {
// Make replay easy: a one-liner using base64 so special chars/newlines in the payload don't break the log line
const base64 = req.body.toString('base64');
const replayCmd = `echo '${base64}' | base64 -d | curl -X POST -H 'Content-Type: application/json' --data-binary @- 'http://localhost:1866${req.originalUrl || req.url}'`;
logger.error('Failed to parse transcript webhook payload even after sanitization', {
jiraKey,
error: parseErr.message,
rawBody: rawBodyStr,
rawBodyBase64: base64,
replayCurl: replayCmd,
// The body after our trimming + escaping attempt (truncated for log size if huge)
sanitizedAttempt: rawBodyStr.substring(0, 2000)
});
// Last-resort fallback: extract fields with regex so we can still attempt
// the attachments and summary comment even if the overall JSON is badly broken
// (common when transcription text contains unescaped quotes, newlines, etc.)
try {
const get = (name) => {
// Simple string value extractor that tolerates some bad escaping inside the value
const re = new RegExp(`"${name}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`, 'i');
const m = rawBodyStr.match(re);
if (!m) return '';
return m[1]
.replace(/\\"/g, '"')
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t')
.replace(/\\\\/g, '\\');
};
payload = {
orgId: get('orgId'),
taskId: get('taskId'),
audioFileName: get('audioFileName'),
audioFilePath: get('audioFilePath'),
textFileName: get('textFileName'),
textFilePath: get('textFilePath'),
Summaries: {
intialContactReason: get('intialContactReason'),
additionalContext: get('additionalContext'),
keyActionsTake: get('keyActionsTake'),
nextSteps: get('nextSteps'),
resolution: get('resolution')
}
};
logger.warn('Used regex fallback extraction for malformed payload', { jiraKey });
} catch (fallbackErr) {
logger.error('Fallback extraction also failed', { jiraKey, error: fallbackErr.message });
return res.status(400).json({ status: "error", message: "Invalid JSON payload from upstream" });
}
}
}
// Convenience unwrap for Postman / local testing.
// When users copy the "payload" value (or the whole meta object) from our previous
// WEBEX_TRANSCRIPT or error logs into Postman, the body often arrives wrapped as
// { jiraKey, ip, payload: <real flat data>, level }.
// Auto-unwrap so the rest of the handler sees the real fields (audioFilePath, Summaries etc.)
// and actually performs the attachments + comment.
if (payload && typeof payload === 'object' && payload.payload &&
(payload.payload.orgId || payload.payload.audioFilePath || payload.payload.Summaries)) {
logger.info('Unwrapped log meta / copied-from-log payload for testing', { jiraKey });
payload = payload.payload;
}
logger.info('Webex Transcript Webhook Received', {
timestamp, jiraKey, ip, payloadKeys: Object.keys(payload)
});
webexLogger.info('WEBEX_TRANSCRIPT', { timestamp, jiraKey, ip, payload });
if (!jiraKey || !/^[A-Z0-9]+-\d+$/.test(jiraKey)) {
return res.status(400).json({ status: "error", message: "Invalid Jira key" });
}
const results = {
attachments: [],
commentPosted: false,
errors: []
};
// Process each step independently so one failure (e.g. 401 scope) doesn't prevent others.
// Always return 200 to the caller; use "partial_error" status if anything failed.
// This prevents the webhook from "crashing" the processing for non-fatal issues.
// 1. Attach Audio File (if present)
if (payload.audioFilePath) {
try {
await jiraService.attachFileToJira(jiraKey, payload.audioFilePath, payload.audioFileName || `audio-${jiraKey}.wav`);
results.attachments.push('audio');
} catch (error) {
logger.error('Audio file attach failed', {
jiraKey,
error: error.response?.data?.message || error.message,
status: error.response?.status,
responseData: error.response?.data
});
results.errors.push({
step: 'audio',
error: error.response?.data?.message || error.message,
status: error.response?.status,
details: error.response?.data
});
}
}
// 2. Attach Text Transcript (only if it actually exists)
if (payload.textFilePath && payload.textFilePath.trim() !== '') {
try {
await jiraService.attachFileToJira(jiraKey, payload.textFilePath, payload.textFileName || `transcript-${jiraKey}.json`);
results.attachments.push('transcript');
// Convert the JSON transcript to human-readable text and upload as well
const readableAttached = await jiraService.attachReadableTranscript(jiraKey, payload.textFilePath, payload.textFileName);
if (readableAttached) {
results.attachments.push('transcript-readable');
}
} catch (error) {
logger.error('Text transcript attach failed', {
jiraKey,
error: error.response?.data?.message || error.message,
status: error.response?.status,
responseData: error.response?.data
});
results.errors.push({
step: 'text',
error: error.response?.data?.message || error.message,
status: error.response?.status,
details: error.response?.data
});
}
}
// 3. Post Summaries as a clean Jira comment (restricted/internal via role)
if (payload.Summaries && Object.keys(payload.Summaries).length > 0) {
try {
// Map internal attachment keys to friendly names for the comment body
const friendly = results.attachments.map(a => {
if (a === 'audio') return 'audio recording';
if (a === 'transcript') return 'JSON transcript';
if (a === 'transcript-readable') return 'human-readable transcript';
return a;
});
await jiraService.postWebexSummaryComment(jiraKey, payload.Summaries, friendly);
results.commentPosted = true;
} catch (error) {
logger.error('Summary comment post failed', {
jiraKey,
error: error.response?.data?.message || error.message,
status: error.response?.status,
responseData: error.response?.data
});
results.errors.push({
step: 'comment',
error: error.response?.data?.message || error.message,
status: error.response?.status,
details: error.response?.data
});
}
}
const overallStatus = results.errors.length > 0 ? 'partial_error' : 'success';
const message = overallStatus === 'success' ? 'Processing completed' : 'Some operations failed (see errors)';
logger.info('Webex webhook processed', { jiraKey, results, overallStatus });
res.status(200).json({
status: overallStatus,
message,
jiraKey,
attachments: results.attachments,
commentPosted: results.commentPosted,
errors: results.errors.length ? results.errors : undefined
});
});
export default router;

135
src/services/grokService.js Normal file
View file

@ -0,0 +1,135 @@
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import OpenAI from 'openai';
import config from '../config/index.js';
import logger from '../utilities/logger.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROMPTS_DIR = path.join(__dirname, '../prompts');
const client = new OpenAI({
apiKey: config.xai.apiKey,
baseURL: config.xai.baseUrl,
});
// Helper to load prompt from file
async function loadPrompt(templateName, variables = {}) {
const filePath = path.join(PROMPTS_DIR, `${templateName}.txt`);
let prompt = await fs.readFile(filePath, 'utf8');
Object.keys(variables).forEach(key => {
const placeholder = new RegExp(`{{${key}}}`, 'g');
prompt = prompt.replace(placeholder, variables[key]);
});
return prompt;
}
// Single ticket summary (your original /getticket endpoint)
export async function generateTicketSummary(context) {
try {
const prompt = await loadPrompt('singleTicketSummary', { context });
const completion = await client.chat.completions.create({
model: "grok-4.20-non-reasoning",
messages: [{ role: "user", content: prompt }],
temperature: 0.2,
max_tokens: 400,
});
return completion.choices[0]?.message?.content?.trim() || "Summary unavailable at this time.";
} catch (err) {
logger.error(`Grok single ticket summary failed: ${err.message}`);
return "AI summary is temporarily unavailable. Please review the ticket directly in Jira.";
}
}
// Multi-ticket summary (your /open-tickets-by-reporter endpoint)
export async function generateOpenTicketsSummary(tickets) {
if (!tickets || tickets.length === 0) {
return [];
}
let context = `Open tickets reported by the customer:\n`;
tickets.forEach(ticket => {
const fields = ticket.fields || {};
const key = ticket.key;
const status = fields.status?.name || "Unknown";
const updated = fields.updated || null;
const title = fields.summary || "No title";
const notes = ticket.enrichedNotes || {};
const description = notes.description || "No description available.";
let commentsText = "";
if (Array.isArray(notes.publicComments) && notes.publicComments.length > 0) {
const recent = notes.publicComments.slice(-8).reverse();
commentsText = "\nRecent public comments (most recent first):\n" + recent.map(c => {
const date = c.createdIso ? c.createdIso.slice(0, 10) : "unknown";
const body = c.body ? c.body.substring(0, 300) : "";
return `[${date}] ${body}${body.length === 300 ? '...' : ''}`;
}).join("\n");
}
context += `\nTicket: ${key}
Status: ${status}
Last updated: ${updated || "unknown"}
Title: ${title}
Initial Problem / Description: ${description}
${commentsText}
`;
});
try {
const prompt = await loadPrompt('openTicketsSummary', { context });
const completion = await client.chat.completions.create({
model: "grok-4.20-non-reasoning",
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
max_tokens: 900,
});
const rawOutput = completion.choices[0]?.message?.content?.trim();
if (!rawOutput) {
return tickets.map(t => createFallbackTicket(t));
}
let parsed;
try {
parsed = JSON.parse(rawOutput);
} catch (e) {
const jsonMatch = rawOutput.match(/\[[\s\S]*\]/);
if (jsonMatch) {
parsed = JSON.parse(jsonMatch[0]);
} else {
parsed = null;
}
}
return Array.isArray(parsed) ? parsed : tickets.map(t => createFallbackTicket(t));
} catch (err) {
logger.error(`Grok multi-ticket summary failed: ${err.message}`);
return tickets.map(t => createFallbackTicket(t));
}
}
// Fallback helper
function createFallbackTicket(ticket) {
const fields = ticket.fields || {};
return {
jiraKey: ticket.key,
currentStatus: fields.status?.name || "Unknown",
summary: `${fields.status?.name || "Unknown"} - ${fields.summary || "No details available. Check public notes for latest updates."}`,
lastUpdated: fields.updated || null
};
}
export default {
generateTicketSummary,
generateOpenTicketsSummary
};

View file

@ -0,0 +1,106 @@
import axios from 'axios';
import { jiraClient } from './jiraService.js';
import config from '../config/index.js';
import logger from '../utilities/logger.js';
export async function getDetailedHealth() {
const startTime = Date.now();
const health = {
status: 'healthy',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
environment: config.nodeEnv || 'development',
version: process.env.npm_package_version || '1.0.0',
checks: {}
};
// Self check
health.checks.self = {
status: 'healthy',
responseTimeMs: Date.now() - startTime
};
// Jira Check (critical) - uses the shared jiraClient so it automatically
// gets the correct base URL (including api.atlassian.com/ex/jira/{cloudId} form)
// and the configured auth (basic kept).
try {
const jiraStart = Date.now();
await jiraClient.get('/rest/api/3/myself', { timeout: 6000 });
health.checks.jira = {
status: 'healthy',
responseTimeMs: Date.now() - jiraStart,
message: 'Jira authentication successful'
};
} catch (err) {
const status = err.response?.status || 'unknown';
health.checks.jira = {
status: (status === 401 || status === 403) ? 'unhealthy' : 'degraded',
responseTimeMs: Date.now() - startTime,
message: status === 401 ? 'Jira authentication failed (check credentials)' : `Jira error (${status})`
};
if (health.status === 'healthy') health.status = 'degraded';
}
// xAI Check - Tolerant + tries both common endpoints
let xaiSuccess = false;
const xaiStart = Date.now();
// Try 1: Legacy chat/completions (still widely used)
try {
await axios.post(`${config.xai.baseUrl}/v1/chat/completions`, {
model: "grok-4.20-non-reasoning",
messages: [{ role: 'user', content: 'Say \'ok\'.' }],
max_tokens: 5,
temperature: 0
}, {
headers: {
Authorization: `Bearer ${config.xai.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 7000
});
xaiSuccess = true;
} catch (_) {}
// Try 2: New Responses API (if legacy fails)
if (!xaiSuccess) {
try {
await axios.post(`${config.xai.baseUrl}/v1/responses`, {
model: "grok-4.20-non-reasoning",
input: [{ role: 'user', content: 'Say \'ok\'.' }],
max_tokens: 5,
temperature: 0
}, {
headers: {
Authorization: `Bearer ${config.xai.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 7000
});
xaiSuccess = true;
} catch (_) {}
}
if (xaiSuccess) {
health.checks.xai = {
status: 'healthy',
responseTimeMs: Date.now() - xaiStart,
message: 'xAI API reachable'
};
} else {
health.checks.xai = {
status: 'degraded',
responseTimeMs: Date.now() - xaiStart,
message: 'xAI API test failed (summaries still working)'
};
// Do NOT degrade overall status for xAI issues
}
// Overall status only fails if Jira is unhealthy
if (health.checks.jira?.status === 'unhealthy') {
health.status = 'unhealthy';
}
return health;
}

1425
src/services/jiraService.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
export function adfToPlainText(node) {
if (!node || typeof node !== 'object') return '';
let text = '';
if (node.type === 'text' && node.text) {
text += node.text;
if (node.marks) {
node.marks.forEach(mark => {
if (mark.type === 'link' && mark.attrs?.href) {
text += ` (${mark.attrs.href})`;
}
});
}
}
if (node.content && Array.isArray(node.content)) {
node.content.forEach(child => {
const childText = adfToPlainText(child);
if (childText) {
text += childText;
if (['paragraph', 'heading', 'bulletList', 'orderedList', 'listItem'].includes(child.type)) {
text += '\n\n';
} else {
text += ' ';
}
}
});
}
return text.trim();
}

60
src/utilities/logger.js Normal file
View file

@ -0,0 +1,60 @@
import winston from 'winston';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const LOG_DIR = path.join(__dirname, '../../logs');
// Ensure logs directory exists
import fs from 'fs';
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
// Console (for development)
new winston.transports.Console({
format: winston.format.simple()
}),
// Main application log file
new winston.transports.File({
filename: path.join(LOG_DIR, 'app.log'),
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
tailable: true
})
]
});
// Dedicated transport for Webex callbacks (human-readable + JSON)
// IMPORTANT: This now also goes to console (stdout) so that `docker logs` and
// container platforms can see the WEBEX_TRANSCRIPT events without needing
// to inspect files inside the container.
const webexLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, message, ...meta }) => {
return `[${timestamp}] ${message} | ${JSON.stringify(meta)}`;
})
),
transports: [
// Console so webhook activity is visible in docker logs / platform logs
new winston.transports.Console(),
new winston.transports.File({
filename: path.join(LOG_DIR, 'webex-callbacks.log'),
maxsize: 20 * 1024 * 1024, // 20MB
maxFiles: 10,
tailable: true
})
]
});
export { logger, webexLogger };
export default logger;

89
ss-fields-266.json Normal file
View file

@ -0,0 +1,89 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Type in short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click on it to select.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Please provide as much detail as possible (ie: What is broken with the device? Is it a specific cord that needs to be replaced? Include all relevant information. )",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10544",
"name": "Affected Device",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10544
},
"visible": true
},
{
"fieldId": "customfield_10294",
"name": "Date/Time Issue Occured",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "datetime",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:datetime",
"customId": 10294
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

115
ss-fields-267.json Normal file
View file

@ -0,0 +1,115 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert Short Description of Issue",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click on it to select.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "customfield_10381",
"name": "Report System",
"description": "Click on the drop down and select how this report is accessed.",
"required": false,
"defaultValues": [],
"validValues": [
{
"value": "11381",
"label": "Cognos",
"children": []
},
{
"value": "11382",
"label": "Metric Insight",
"children": []
},
{
"value": "11383",
"label": "Tableau",
"children": []
},
{
"value": "11384",
"label": "Stores Productivity Report",
"children": []
},
{
"value": "11385",
"label": "Insights to Action (ITA)",
"children": []
}
],
"jiraSchema": {
"type": "option",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:select",
"customId": 10381
},
"visible": true
},
{
"fieldId": "customfield_10480",
"name": "Report Name",
"description": "What is the name of the report?",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10480
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Describe the issue you are having. The more info you provide will help us get you help faster!",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

202
ss-fields-268.json Normal file
View file

@ -0,0 +1,202 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and select",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "customfield_10548",
"name": "Reason for Omni Request",
"description": "Click the 1st drop down to select what needs enabled / disabled. Click the 2nd drop down to select reason code.",
"required": true,
"defaultValues": [],
"validValues": [
{
"value": "13461",
"label": "BOSS Enable",
"children": [
{
"value": "13467",
"label": "Printer Issues Resolved",
"children": []
},
{
"value": "13468",
"label": "NSO/Maintenance Completed",
"children": []
},
{
"value": "13469",
"label": "Other",
"children": []
}
]
},
{
"value": "13462",
"label": "BOSS Disable",
"children": [
{
"value": "13470",
"label": "Printer Issues",
"children": []
},
{
"value": "13471",
"label": "NSO/Maintenance",
"children": []
},
{
"value": "13472",
"label": "Other",
"children": []
}
]
},
{
"value": "13463",
"label": "Pickup Enable",
"children": [
{
"value": "13473",
"label": "Printer Issues Resolved",
"children": []
},
{
"value": "13474",
"label": "NSO/Maintenance Completed",
"children": []
},
{
"value": "13475",
"label": "Other",
"children": []
}
]
},
{
"value": "13464",
"label": "Pickup Disable",
"children": [
{
"value": "13476",
"label": "Printer Issues",
"children": []
},
{
"value": "13477",
"label": "NSO/Maintenance",
"children": []
},
{
"value": "13478",
"label": "Other",
"children": []
}
]
},
{
"value": "13465",
"label": "BOSS and Pickup Enable",
"children": [
{
"value": "13479",
"label": "Printer Issues Resolved",
"children": []
},
{
"value": "13480",
"label": "NSO/Maintenance Completed",
"children": []
},
{
"value": "13481",
"label": "Other",
"children": []
}
]
},
{
"value": "13466",
"label": "BOSS and Pickup Disable",
"children": [
{
"value": "13482",
"label": "Printer Issues",
"children": []
},
{
"value": "13483",
"label": "NSO/Maintenance",
"children": []
},
{
"value": "13484",
"label": "Other",
"children": []
}
]
}
],
"jiraSchema": {
"type": "option-with-child",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect",
"customId": 10548
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "If you selected \"Other\" as the Reason Code above please provide detail here. Otherwise please briefly describe why you need Omni Turn Off / On Completed.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

89
ss-fields-269.json Normal file
View file

@ -0,0 +1,89 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert Short Description of Issue",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click on it to select.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "What is the issue? Give examples of what is happening? What troubleshooting have you tried?",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10294",
"name": "Date/Time Issue Occured",
"description": "Date and Time of Incident.",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "datetime",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:datetime",
"customId": 10294
},
"visible": true
},
{
"fieldId": "customfield_10557",
"name": "Transaction Detail",
"description": "Provide transaction details to ensure the correct information is reviewed.",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10557
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

105
ss-fields-270.json Normal file
View file

@ -0,0 +1,105 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click on it to select.",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Describe the issue you are having. The more info you provide will help us get you help faster!",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10454",
"name": "Preferred Contact Method",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [
{
"value": "11325",
"label": "Email",
"children": []
},
{
"value": "11326",
"label": "Phone",
"children": []
},
{
"value": "11327",
"label": "Webex",
"children": []
}
],
"jiraSchema": {
"type": "option",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:select",
"customId": 10454
},
"visible": true
},
{
"fieldId": "customfield_10426",
"name": "Phone",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10426
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

89
ss-fields-271.json Normal file
View file

@ -0,0 +1,89 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click on it to select.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "When did you start noticing this issue? Is this an issue daily? Does traffic tend to “catch up” after some time?",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10294",
"name": "Date/Time Issue Occured",
"description": "What date / time did you first notice that there was a traffic issue?",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "datetime",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:datetime",
"customId": 10294
},
"visible": true
},
{
"fieldId": "customfield_10544",
"name": "Affected Device",
"description": "Do you have two traffic counters? Which traffic counter is having an issue?",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10544
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

75
ss-fields-272.json Normal file
View file

@ -0,0 +1,75 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and select",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Please provide detailed description of Sterling Application issue. Please make sure to include any order information available",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10294",
"name": "Date/Time Issue Occured",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "datetime",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:datetime",
"customId": 10294
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "Please include a screenshot of what you are showing related to your Sterling Issue",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

103
ss-fields-273.json Normal file
View file

@ -0,0 +1,103 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and click to select it from the list.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Please provide as much detail as possible regarding the missing hardware.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10544",
"name": "Affected Device",
"description": "What device / hardware are you missing?",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10544
},
"visible": true
},
{
"fieldId": "customfield_10549",
"name": "Police Report Number",
"description": "A Police Report needs to be filed for any lost/stolen hardware. Please make sure to include this.",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10549
},
"visible": true
},
{
"fieldId": "customfield_10294",
"name": "Date / Time Hardware Last Seen or Used",
"description": "When is the last day / time this device was used or in your store?",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "datetime",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:datetime",
"customId": 10294
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

61
ss-fields-274.json Normal file
View file

@ -0,0 +1,61 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of request.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and select",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Please provide as much detail as possible for why your store needs additional hardware.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

105
ss-fields-275.json Normal file
View file

@ -0,0 +1,105 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "Insert short description of issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "Type in your store number and select",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "description",
"name": "Description",
"description": "Please provide as much detail as possible about your login issue.",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "customfield_10454",
"name": "Preferred Contact Method",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [
{
"value": "11325",
"label": "Email",
"children": []
},
{
"value": "11326",
"label": "Phone",
"children": []
},
{
"value": "11327",
"label": "Webex",
"children": []
}
],
"jiraSchema": {
"type": "option",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:select",
"customId": 10454
},
"visible": true
},
{
"fieldId": "customfield_10426",
"name": "Phone",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textfield",
"customId": 10426
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

103
ss-fields-426.json Normal file
View file

@ -0,0 +1,103 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "customfield_10563",
"name": "Description",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:textarea",
"customId": 10563
},
"visible": true
},
{
"fieldId": "customfield_10264",
"name": "Urgency",
"description": "",
"required": false,
"defaultValues": [
{
"value": "10480",
"label": "Medium",
"children": []
}
],
"validValues": [
{
"value": "10478",
"label": "Critical",
"children": []
},
{
"value": "10479",
"label": "High",
"children": []
},
{
"value": "10480",
"label": "Medium",
"children": []
},
{
"value": "10481",
"label": "Low",
"children": []
}
],
"jiraSchema": {
"type": "option",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:select",
"customId": 10264
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

216
ss-fields-493.json Normal file
View file

@ -0,0 +1,216 @@
{
"requestTypeFields": [
{
"fieldId": "summary",
"name": "Summary",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "summary"
},
"visible": true
},
{
"fieldId": "customfield_10261",
"name": "Store Number",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "cmdb-object-field",
"custom": "com.atlassian.jira.plugins.cmdb:cmdb-object-cftype",
"customId": 10261
},
"visible": true
},
{
"fieldId": "customfield_11688",
"name": "Reason for Transportation Request",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [
{
"value": "13706",
"label": "Urgent Store Closure",
"children": [
{
"value": "13711",
"label": "Power Outage",
"children": []
},
{
"value": "13712",
"label": "Fire",
"children": []
},
{
"value": "13713",
"label": "Mall Closure",
"children": []
},
{
"value": "13714",
"label": "Weather Hazard",
"children": []
},
{
"value": "13715",
"label": "Unexpected Closure",
"children": []
}
]
},
{
"value": "13707",
"label": "Unprofessional Driver Issue",
"children": [
{
"value": "13716",
"label": "Unsafe / Hostile Work Environment",
"children": []
},
{
"value": "13717",
"label": "Profanity / Offensive Language",
"children": []
},
{
"value": "13718",
"label": "Receiving shipment anywhere but back room stock door",
"children": []
}
]
},
{
"value": "13708",
"label": "Carrier Issue",
"children": [
{
"value": "13719",
"label": "Delivering outside of delivery window (includes after 5p & Saturday)",
"children": []
},
{
"value": "13720",
"label": "Unprofessional Driver",
"children": []
},
{
"value": "13721",
"label": "Damaged Carton (if issue is consistently happening)",
"children": []
},
{
"value": "13722",
"label": "Delivery not meeting company standard / policies",
"children": []
},
{
"value": "13723",
"label": "Picking up outside of pickup window provided",
"children": []
}
]
},
{
"value": "13709",
"label": "Missed Pickup",
"children": [
{
"value": "13724",
"label": "BOSS FedEx Express",
"children": []
},
{
"value": "13725",
"label": "BOSS FedEx Ground",
"children": []
},
{
"value": "13726",
"label": "BOSS USPS",
"children": []
},
{
"value": "13727",
"label": "FedEx Ground (Transfer)",
"children": []
},
{
"value": "13728",
"label": "FedEx Express (Transfer)",
"children": []
},
{
"value": "13729",
"label": "CA Post (BOSS)",
"children": []
},
{
"value": "13730",
"label": "Purolator (BOSS)",
"children": []
},
{
"value": "13731",
"label": "UPS",
"children": []
}
]
},
{
"value": "13710",
"label": "Ship Manager Issue",
"children": [
{
"value": "13732",
"label": "Unable to Login - Fedex Ship Manager or UPS Campus Ship Manager (CANADA ONLY)",
"children": []
}
]
}
],
"jiraSchema": {
"type": "option-with-child",
"custom": "com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect",
"customId": 11688
},
"visible": true
},
{
"fieldId": "description",
"name": "Detailed Description",
"description": "",
"required": true,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "string",
"system": "description"
},
"visible": true
},
{
"fieldId": "attachment",
"name": "Attachment",
"description": "",
"required": false,
"defaultValues": [],
"validValues": [],
"jiraSchema": {
"type": "array",
"items": "attachment",
"system": "attachment"
},
"visible": true
}
],
"canRaiseOnBehalfOf": true,
"canAddRequestParticipants": true
}

209
ss-request-types-clean.json Normal file
View file

@ -0,0 +1,209 @@
[
{
"id": "266",
"name": "Broken Device / Hardware",
"defaultName": "Broken Device / Hardware",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"156",
"157"
],
"canCreateRequest": true
},
{
"id": "267",
"name": "Business Report Issue",
"defaultName": "Business Report Issue",
"description": "Business Report not updating or showing incorrect data? Click here",
"issueTypeId": "10085",
"groupIds": [
"156",
"158"
],
"canCreateRequest": true
},
{
"id": "268",
"name": "Omni Turn Off / On",
"defaultName": "Omni Turn Off / On",
"description": "",
"issueTypeId": "10082",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "269",
"name": "Register Not Functioning Properly",
"defaultName": "Register Not Functioning Properly",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"156"
],
"canCreateRequest": true
},
{
"id": "270",
"name": "Report a Technology Issue",
"defaultName": "Report a Technology Issue",
"description": "Select this option if something you use for work that usually functions, is not functioning as expected.",
"issueTypeId": "10085",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "271",
"name": "Report a Traffic Counter Issue",
"defaultName": "Report a Traffic Counter Issue",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "272",
"name": "Report an Issue with Sterling Application",
"defaultName": "Report an Issue with Sterling Application",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "273",
"name": "Report Missing Hardware",
"defaultName": "Report Missing Hardware",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"157"
],
"canCreateRequest": true
},
{
"id": "274",
"name": "Request Additional Hardware",
"defaultName": "Request Additional Hardware",
"description": "",
"issueTypeId": "10082",
"groupIds": [
"157"
],
"canCreateRequest": true
},
{
"id": "493",
"name": "Store Transportation Request",
"defaultName": "Store Transportation Request",
"description": "Click here to report an issue / request assistance with store transportation.",
"issueTypeId": "10085",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "426",
"name": "UKG Pro / Workforce Management Issues",
"defaultName": "UKG Pro / Workforce Management Issues",
"description": "",
"issueTypeId": "10085",
"groupIds": [
"158"
],
"canCreateRequest": true
},
{
"id": "275",
"name": "Unable to Login",
"defaultName": "Unable to Login",
"description": "Try the AEO2GO.com password reset and [unlock|https://aeo.onelogin.com/login2#action=unlock_account] options!\n[Forgot Password| https://aeo.onelogin.com/login2#action=password_reset]\n[Instructions| https://wiki.ae.com/display/KB/AEO2GO+-+How+to+Unlock+Your+SSO+Network+Account]\n\nIt takes seconds to regain access! If that does not work, select this option.",
"issueTypeId": "10085",
"groupIds": [
"156",
"158"
],
"canCreateRequest": true
},
{
"id": "276",
"name": "Bug Resolution",
"defaultName": "Bug Resolution",
"description": "",
"issueTypeId": "10079",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "263",
"name": "Emailed request",
"defaultName": "Emailed request",
"description": "Request received from your email support channel.",
"issueTypeId": "10082",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "359",
"name": "Hardware Request",
"defaultName": "Hardware Request",
"description": "",
"issueTypeId": "10091",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "393",
"name": "Payment Table Validation ",
"defaultName": "Payment Table Validation ",
"description": "",
"issueTypeId": "10082",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "277",
"name": "Process Problem",
"defaultName": "Process Problem",
"description": "",
"issueTypeId": "10079",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "494",
"name": "Questions",
"defaultName": "Questions",
"description": "Request type used by live chat for fallback, only when other request types don't work.",
"issueTypeId": "10008",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "278",
"name": "Recurring Incident",
"defaultName": "Recurring Incident",
"description": "",
"issueTypeId": "10079",
"groupIds": [],
"canCreateRequest": true
},
{
"id": "280",
"name": "SolarWinds Alert",
"defaultName": "SolarWinds Alert",
"description": "",
"issueTypeId": "10085",
"groupIds": [],
"canCreateRequest": true
}
]

643
ss-request-types.json Normal file
View file

@ -0,0 +1,643 @@
{
"_expands": [
"field"
],
"size": 20,
"start": 0,
"limit": 100,
"isLastPage": true,
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype?limit=100",
"base": "https://aeo.atlassian.net",
"context": ""
},
"values": [
{
"_expands": [
"field"
],
"id": "266",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/266"
},
"name": "Broken Device / Hardware",
"description": "",
"helpText": "",
"defaultName": "Broken Device / Hardware",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"156",
"157"
],
"icon": {
"id": "10472",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "267",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/267"
},
"name": "Business Report Issue",
"description": "Business Report not updating or showing incorrect data? Click here",
"helpText": "",
"defaultName": "Business Report Issue",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"156",
"158"
],
"icon": {
"id": "10492",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10492?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10492?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10492?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10492?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "268",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/268"
},
"name": "Omni Turn Off / On",
"description": "",
"helpText": "",
"defaultName": "Omni Turn Off / On",
"issueTypeId": "10082",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10487",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10487?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10487?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10487?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10487?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "269",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/269"
},
"name": "Register Not Functioning Properly",
"description": "",
"helpText": "",
"defaultName": "Register Not Functioning Properly",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"156"
],
"icon": {
"id": "10484",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10484?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10484?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10484?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10484?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "270",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/270"
},
"name": "Report a Technology Issue",
"description": "Select this option if something you use for work that usually functions, is not functioning as expected.",
"helpText": "",
"defaultName": "Report a Technology Issue",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10470",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10470?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10470?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10470?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10470?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "271",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/271"
},
"name": "Report a Traffic Counter Issue",
"description": "",
"helpText": "",
"defaultName": "Report a Traffic Counter Issue",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10648",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10648?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10648?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10648?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10648?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "272",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/272"
},
"name": "Report an Issue with Sterling Application",
"description": "",
"helpText": "",
"defaultName": "Report an Issue with Sterling Application",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10469",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10469?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10469?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10469?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10469?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "273",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/273"
},
"name": "Report Missing Hardware",
"description": "",
"helpText": "",
"defaultName": "Report Missing Hardware",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"157"
],
"icon": {
"id": "10489",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10489?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10489?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10489?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10489?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "274",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/274"
},
"name": "Request Additional Hardware",
"description": "",
"helpText": "",
"defaultName": "Request Additional Hardware",
"issueTypeId": "10082",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"157"
],
"icon": {
"id": "10511",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "493",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/493"
},
"name": "Store Transportation Request",
"description": "Click here to report an issue / request assistance with store transportation.",
"helpText": "",
"defaultName": "Store Transportation Request",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10664",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10664?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10664?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10664?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10664?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "426",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/426"
},
"name": "UKG Pro / Workforce Management Issues",
"description": "",
"helpText": "",
"defaultName": "UKG Pro / Workforce Management Issues",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"158"
],
"icon": {
"id": "10499",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10499?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10499?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10499?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10499?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "275",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/275"
},
"name": "Unable to Login",
"description": "Try the AEO2GO.com password reset and [unlock|https://aeo.onelogin.com/login2#action=unlock_account] options!\n[Forgot Password| https://aeo.onelogin.com/login2#action=password_reset]\n[Instructions| https://wiki.ae.com/display/KB/AEO2GO+-+How+to+Unlock+Your+SSO+Network+Account]\n\nIt takes seconds to regain access! If that does not work, select this option.",
"helpText": "",
"defaultName": "Unable to Login",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [
"156",
"158"
],
"icon": {
"id": "10610",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10610?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10610?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10610?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10610?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "276",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/276"
},
"name": "Bug Resolution",
"description": "",
"helpText": "",
"defaultName": "Bug Resolution",
"issueTypeId": "10079",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10476",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10476?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10476?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10476?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10476?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "263",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/263"
},
"name": "Emailed request",
"description": "Request received from your email support channel.",
"helpText": "",
"defaultName": "Emailed request",
"issueTypeId": "10082",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10527",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10527?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10527?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10527?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10527?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "359",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/359"
},
"name": "Hardware Request",
"description": "",
"helpText": "",
"defaultName": "Hardware Request",
"issueTypeId": "10091",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10511",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "393",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/393"
},
"name": "Payment Table Validation ",
"description": "",
"helpText": "",
"defaultName": "Payment Table Validation ",
"issueTypeId": "10082",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10471",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10471?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10471?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10471?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10471?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "277",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/277"
},
"name": "Process Problem",
"description": "",
"helpText": "",
"defaultName": "Process Problem",
"issueTypeId": "10079",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10472",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10472?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "494",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/494"
},
"name": "Questions",
"description": "Request type used by live chat for fallback, only when other request types don't work.",
"helpText": "",
"defaultName": "Questions",
"issueTypeId": "10008",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10466",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10466?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10466?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10466?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10466?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "278",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/278"
},
"name": "Recurring Incident",
"description": "",
"helpText": "",
"defaultName": "Recurring Incident",
"issueTypeId": "10079",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10507",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10507?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10507?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10507?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10507?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
},
{
"_expands": [
"field"
],
"id": "280",
"_links": {
"self": "https://aeo.atlassian.net/rest/servicedeskapi/servicedesk/170/requesttype/280"
},
"name": "SolarWinds Alert",
"description": "",
"helpText": "",
"defaultName": "SolarWinds Alert",
"issueTypeId": "10085",
"serviceDeskId": "170",
"portalId": "170",
"groupIds": [],
"icon": {
"id": "10511",
"_links": {
"iconUrls": {
"48x48": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=large",
"24x24": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=small",
"16x16": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=xsmall",
"32x32": "https://aeo.atlassian.net/rest/api/2/universal_avatar/view/type/SD_REQTYPE/avatar/10511?size=medium"
}
}
},
"restrictionStatus": "OPEN",
"canCreateRequest": true
}
]
}