ofsc-utility 1.0.38 → 1.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,4 +6,5 @@ declare const OfscUserUtility: {
6
6
  downloadAllInactiveUsersCSV: typeof downloadAllInactiveUsersCSV;
7
7
  };
8
8
  export declare function downloadAllInactiveUsersCSV(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<void>;
9
+ export declare function downloadAllInactiveUsers(clientId: string, clientSecret: string, instanceUrl: string, inactivityThresholdDays?: number): Promise<Record<string, any>[]>;
9
10
  export default OfscUserUtility;
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.generateUsersCollaborationCSV = void 0;
40
40
  exports.downloadAllUsersCSV = downloadAllUsersCSV;
41
41
  exports.downloadAllInactiveUsersCSV = downloadAllInactiveUsersCSV;
42
+ exports.downloadAllInactiveUsers = downloadAllInactiveUsers;
42
43
  const fs = __importStar(require("fs"));
43
44
  const node_fetch_1 = __importDefault(require("node-fetch"));
44
45
  const index_1 = require("../oauthTokenService/index");
@@ -48,12 +49,12 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
48
49
  const limit = 100;
49
50
  let allItems = [];
50
51
  let totalFetched = 0;
51
- console.log("🚀 Starting users download...");
52
+ console.log("Starting users download...");
52
53
  console.log("-------------------------------------");
53
54
  while (true) {
54
55
  const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
55
56
  const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
56
- console.log(`➡️ Fetching offset=${offset} limit=${limit}`);
57
+ console.log(`Fetching offset=${offset} limit=${limit}`);
57
58
  const res = await (0, node_fetch_1.default)(url, {
58
59
  method: "GET",
59
60
  headers: {
@@ -62,18 +63,18 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
62
63
  }
63
64
  });
64
65
  if (!res.ok) {
65
- throw new Error(`❌ Fetch failed: ${res.status} ${res.statusText}`);
66
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
66
67
  }
67
68
  const data = (await res.json());
68
69
  allItems.push(...data.items);
69
70
  totalFetched += data.items.length;
70
- console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
71
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
71
72
  if (offset + limit >= data.totalResults)
72
73
  break;
73
74
  offset += limit;
74
75
  }
75
76
  console.log("-------------------------------------");
76
- console.log("🧩 Collecting all unique properties...");
77
+ console.log("Collecting all unique properties...");
77
78
  // Collect union of all properties
78
79
  const allProperties = new Set();
79
80
  for (const item of allItems) {
@@ -84,7 +85,7 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
84
85
  }
85
86
  }
86
87
  const headers = Array.from(allProperties);
87
- console.log(`📝 Total unique fields: ${headers.length}`);
88
+ console.log(`Total unique fields: ${headers.length}`);
88
89
  // Build CSV rows
89
90
  const csvRows = [];
90
91
  csvRows.push(headers.join(","));
@@ -109,11 +110,11 @@ async function downloadAllUsersCSV(clientId, clientSecret, instanceUrl) {
109
110
  const filePath = "./users.csv";
110
111
  fs.writeFileSync(filePath, csvRows.join("\n"));
111
112
  console.log("-------------------------------------");
112
- console.log("Users CSV Created Successfully!");
113
- console.log(`📁 File: ${filePath}`);
114
- console.log(`📦 Total Records: ${totalFetched}`);
115
- console.log(`🧩 Total Columns (Dynamic): ${headers.length}`);
116
- console.log(`🧩 Date Time: ${new Date()}`);
113
+ console.log("Users CSV Created Successfully!");
114
+ console.log(`File: ${filePath}`);
115
+ console.log(`Total Records: ${totalFetched}`);
116
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
117
+ console.log(`Date Time: ${new Date()}`);
117
118
  console.log("-------------------------------------");
118
119
  }
119
120
  var collaborationGroups_1 = require("./collaborationGroups");
@@ -141,12 +142,12 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
141
142
  const limit = 100;
142
143
  let allItems = [];
143
144
  let totalFetched = 0;
144
- console.log("🚀 Starting users download...");
145
+ console.log("Starting users download...");
145
146
  console.log("-------------------------------------");
146
147
  while (true) {
147
148
  const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
148
149
  const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
149
- console.log(`➡️ Fetching offset=${offset} limit=${limit}`);
150
+ console.log(`Fetching offset=${offset} limit=${limit}`);
150
151
  const res = await (0, node_fetch_1.default)(url, {
151
152
  method: "GET",
152
153
  headers: {
@@ -155,23 +156,23 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
155
156
  }
156
157
  });
157
158
  if (!res.ok) {
158
- throw new Error(`❌ Fetch failed: ${res.status} ${res.statusText}`);
159
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
159
160
  }
160
161
  const data = (await res.json());
161
162
  allItems.push(...data.items);
162
163
  totalFetched += data.items.length;
163
- console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
164
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
164
165
  if (offset + limit >= data.totalResults)
165
166
  break;
166
167
  offset += limit;
167
168
  }
168
169
  console.log("-------------------------------------");
169
- console.log(`🔎 Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
170
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
170
171
  const now = new Date();
171
172
  const inactiveItems = allItems.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
172
- console.log(` ${inactiveItems.length} of ${allItems.length} users are inactive`);
173
+ console.log(` ${inactiveItems.length} of ${allItems.length} users are inactive`);
173
174
  console.log("-------------------------------------");
174
- console.log("🧩 Collecting all unique properties...");
175
+ console.log("Collecting all unique properties...");
175
176
  // Collect union of all properties (from inactive users only)
176
177
  const allProperties = new Set();
177
178
  for (const item of inactiveItems) {
@@ -182,7 +183,7 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
182
183
  }
183
184
  }
184
185
  const headers = Array.from(allProperties);
185
- console.log(`📝 Total unique fields: ${headers.length}`);
186
+ console.log(`Total unique fields: ${headers.length}`);
186
187
  // Build CSV rows
187
188
  const csvRows = [];
188
189
  csvRows.push(headers.join(","));
@@ -207,12 +208,87 @@ async function downloadAllInactiveUsersCSV(clientId, clientSecret, instanceUrl,
207
208
  const filePath = "./inactive_users.csv";
208
209
  fs.writeFileSync(filePath, csvRows.join("\n"));
209
210
  console.log("-------------------------------------");
210
- console.log("Inactive Users CSV Created Successfully!");
211
- console.log(`📁 File: ${filePath}`);
212
- console.log(`📦 Total Fetched: ${totalFetched}`);
213
- console.log(`🚫 Inactive Records (>${inactivityThresholdDays} days): ${inactiveItems.length}`);
214
- console.log(`🧩 Total Columns (Dynamic): ${headers.length}`);
215
- console.log(`🧩 Date Time: ${new Date()}`);
211
+ console.log("Inactive Users CSV Created Successfully!");
212
+ console.log(`File: ${filePath}`);
213
+ console.log(`Total Fetched: ${totalFetched}`);
214
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${inactiveItems.length}`);
215
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
216
+ console.log(`Date Time: ${new Date()}`);
216
217
  console.log("-------------------------------------");
217
218
  }
219
+ async function downloadAllInactiveUsers(clientId, clientSecret, instanceUrl, inactivityThresholdDays = 14) {
220
+ let offset = 0;
221
+ const limit = 100;
222
+ let allItems = [];
223
+ let totalFetched = 0;
224
+ console.log("Starting users download...");
225
+ console.log("-------------------------------------");
226
+ while (true) {
227
+ const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/users/?offset=${offset}&limit=${limit}`;
228
+ const token = await (0, index_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
229
+ console.log(`Fetching offset=${offset} limit=${limit}`);
230
+ const res = await (0, node_fetch_1.default)(url, {
231
+ method: "GET",
232
+ headers: {
233
+ Authorization: `Bearer ${token}`,
234
+ Accept: "application/json"
235
+ }
236
+ });
237
+ if (!res.ok) {
238
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
239
+ }
240
+ const data = (await res.json());
241
+ allItems.push(...data.items);
242
+ totalFetched += data.items.length;
243
+ console.log(` Received ${data.items.length} items (Total: ${totalFetched})`);
244
+ if (offset + limit >= data.totalResults)
245
+ break;
246
+ offset += limit;
247
+ }
248
+ console.log("-------------------------------------");
249
+ console.log(`Filtering inactive users (no login in last ${inactivityThresholdDays} days)...`);
250
+ const now = new Date();
251
+ // Skip users with blank/missing lastLoginTime (never-login users are ignored entirely)
252
+ const usersWithLogin = allItems.filter(item => !!(0, index_2.parseOFSCDate)(item.lastLoginTime));
253
+ const skippedNeverLoggedIn = allItems.length - usersWithLogin.length;
254
+ const inactiveItems = usersWithLogin.filter(item => isInactiveUser(item, inactivityThresholdDays, now));
255
+ console.log(` Skipped (never logged in / blank lastLoginTime): ${skippedNeverLoggedIn}`);
256
+ console.log(` ${inactiveItems.length} of ${usersWithLogin.length} users with login history are inactive`);
257
+ console.log("-------------------------------------");
258
+ console.log("Collecting all unique properties...");
259
+ // Collect union of all properties (from inactive users only)
260
+ const allProperties = new Set();
261
+ for (const item of inactiveItems) {
262
+ for (const key of Object.keys(item)) {
263
+ if (!["resources", "collaborationGroups", "resourceInternalIds", "links"].includes(key)) {
264
+ allProperties.add(key);
265
+ }
266
+ }
267
+ }
268
+ const headers = Array.from(allProperties);
269
+ console.log(`Total unique fields: ${headers.length}`);
270
+ // Build array of plain objects (one per inactive user)
271
+ const result = inactiveItems.map(item => {
272
+ const obj = {};
273
+ for (const field of headers) {
274
+ let value = item[field];
275
+ if (field === "keys") {
276
+ obj[field] = Array.isArray(value) ? value.join("|") : (value ?? "");
277
+ continue;
278
+ }
279
+ // Keep objects as objects (no JSON-stringify/escaping needed since this isn't CSV)
280
+ obj[field] = value !== undefined ? value : null;
281
+ }
282
+ return obj;
283
+ });
284
+ console.log("-------------------------------------");
285
+ console.log("Inactive Users Collected Successfully!");
286
+ console.log(`Total Fetched: ${totalFetched}`);
287
+ console.log(`Skipped (never logged in): ${skippedNeverLoggedIn}`);
288
+ console.log(`Inactive Records (>${inactivityThresholdDays} days): ${result.length}`);
289
+ console.log(`Total Columns (Dynamic): ${headers.length}`);
290
+ console.log(`Date Time: ${new Date()}`);
291
+ console.log("-------------------------------------");
292
+ return result;
293
+ }
218
294
  exports.default = OfscUserUtility;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofsc-utility",
3
- "version": "1.0.38",
3
+ "version": "1.0.39",
4
4
  "description": "TypeScript helpers for Oracle Field Service Cloud (OFSC): events, resources, inventories, metadata and CSV/Excel utilities.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -144,8 +144,163 @@ await ofs.downloadAllUsersCSV(
144
144
  process.env.CLIENT_SECRET,
145
145
  process.env.INSTANCE_NAME,
146
146
  );
147
+
148
+ await ofs.downloadAllInactiveUsersCSV(
149
+ process.env.CLIENT_ID,
150
+ process.env.CLIENT_SECRET,
151
+ process.env.INSTANCE_NAME,
152
+ 14,
153
+ );
154
+ ```
155
+
156
+ #### Download inactive users
157
+
158
+ `downloadAllInactiveUsersCSV` downloads all users from the OFSC Users API, filters
159
+ the results, and writes the matching users to `./inactive_users.csv`.
160
+
161
+ A user is considered inactive when either:
162
+
163
+ - `lastLoginTime` is missing or cannot be parsed, or
164
+ - the time since `lastLoginTime` is greater than `inactivityThresholdDays`
165
+
166
+ The threshold defaults to `14` days. The comparison uses the current date and
167
+ time when the function runs. A user whose last login is exactly at the threshold
168
+ is not included because the function uses a strictly greater-than comparison.
169
+
170
+ ```js
171
+ const ofs = require("ofsc-utility");
172
+
173
+ async function downloadInactiveUsers() {
174
+ const clientId = process.env.CLIENT_ID;
175
+ const clientSecret = process.env.CLIENT_SECRET;
176
+ const instanceUrl = process.env.INSTANCE_NAME;
177
+
178
+ if (!clientId || !clientSecret || !instanceUrl) {
179
+ throw new Error(
180
+ "Set CLIENT_ID, CLIENT_SECRET and INSTANCE_NAME before running this example",
181
+ );
182
+ }
183
+
184
+ await ofs.downloadAllInactiveUsersCSV(
185
+ clientId,
186
+ clientSecret,
187
+ instanceUrl,
188
+ 14,
189
+ );
190
+ }
191
+
192
+ downloadInactiveUsers().catch((error) => {
193
+ console.error("Failed to download inactive users:", error);
194
+ process.exit(1);
195
+ });
147
196
  ```
148
197
 
198
+ **Function signature**
199
+
200
+ ```ts
201
+ downloadAllInactiveUsersCSV(
202
+ clientId: string,
203
+ clientSecret: string,
204
+ instanceUrl: string,
205
+ inactivityThresholdDays?: number,
206
+ ): Promise<void>
207
+ ```
208
+
209
+ **Parameters**
210
+
211
+ - `clientId`: OFSC OAuth client ID.
212
+ - `clientSecret`: OFSC OAuth client secret.
213
+ - `instanceUrl`: OFSC instance name only, such as `mycompany`. Do not include
214
+ `https://` or `.fs.ocs.oraclecloud.com`.
215
+ - `inactivityThresholdDays`: optional non-negative number of days. Defaults to
216
+ `14`.
217
+
218
+ **Output**
219
+
220
+ - File: `./inactive_users.csv`, relative to the process working directory.
221
+ - The CSV contains one row per inactive user.
222
+ - Nested fields such as `resources`, `collaborationGroups`,
223
+ `resourceInternalIds`, and `links` are excluded.
224
+ - The remaining columns are generated dynamically from the fields returned by
225
+ the API. `keys` arrays are joined with `|`; other objects are serialized as
226
+ JSON strings.
227
+
228
+ The helper retrieves users in pages of 100 records and requires valid OFSC API
229
+ credentials. It does not return the CSV contents; successful completion resolves
230
+ to `void`.
231
+
232
+ To run the repository test script against a built distribution:
233
+
234
+ ```bash
235
+ npm run build
236
+ export CLIENT_ID=yourClientId
237
+ export CLIENT_SECRET=yourClientSecret
238
+ export INSTANCE_URL=yourInstanceName
239
+ export INACTIVITY_THRESHOLD_DAYS=14
240
+ node scripts/test-download-inactive-users.js
241
+ ```
242
+
243
+ `INACTIVITY_THRESHOLD_DAYS` is optional in the test script and defaults to `14`.
244
+
245
+ #### Collect inactive users
246
+
247
+ `downloadAllInactiveUsers` downloads users from the OFSC Users API, filters out
248
+ users whose last login is within the inactivity threshold, and returns the
249
+ matching users as an array of plain objects. It does not create a CSV file.
250
+
251
+ Unlike `downloadAllInactiveUsersCSV`, users with a blank(never loggedin)
252
+ `lastLoginTime` are skipped. The threshold defaults to `14` days, and a user is
253
+ included only when the time since the last login is strictly greater than the
254
+ threshold.
255
+
256
+ ```js
257
+ const ofs = require("ofsc-utility");
258
+
259
+ async function collectInactiveUsers() {
260
+ const users = await ofs.User.downloadAllInactiveUsers(
261
+ process.env.CLIENT_ID,
262
+ process.env.CLIENT_SECRET,
263
+ process.env.INSTANCE_NAME,
264
+ 14,
265
+ );
266
+
267
+ console.log(`Collected ${users.length} inactive users`);
268
+ console.log(users[0]);
269
+ }
270
+
271
+ collectInactiveUsers().catch((error) => {
272
+ console.error("Failed to collect inactive users:", error);
273
+ process.exit(1);
274
+ });
275
+ ```
276
+
277
+ **Function signature**
278
+
279
+ ```ts
280
+ downloadAllInactiveUsers(
281
+ clientId: string,
282
+ clientSecret: string,
283
+ instanceUrl: string,
284
+ inactivityThresholdDays?: number,
285
+ ): Promise<Record<string, any>[]>
286
+ ```
287
+
288
+ **Parameters and result**
289
+
290
+ - `clientId`: OFSC OAuth client ID.
291
+ - `clientSecret`: OFSC OAuth client secret.
292
+ - `instanceUrl`: OFSC instance name only, such as `mycompany`. Do not include
293
+ `https://` or `.fs.ocs.oraclecloud.com`.
294
+ - `inactivityThresholdDays`: optional non-negative number of days. Defaults to
295
+ `14`.
296
+ - The returned array contains one object per inactive user.
297
+ - Nested fields such as `resources`, `collaborationGroups`,
298
+ `resourceInternalIds`, and `links` are excluded.
299
+ - `keys` arrays are joined with `|`; other object values remain objects.
300
+
301
+ The helper retrieves users in pages of 100 records and requires valid OFSC API
302
+ credentials.
303
+
149
304
  ### Resource related methods
150
305
 
151
306
  ```js
@@ -285,6 +440,7 @@ Top-level exports include:
285
440
  - `downloadWorkZoneCSV`
286
441
  - `downloadAllResourcesCSV`
287
442
  - `downloadAllUsersCSV`
443
+ - `downloadAllInactiveUsersCSV`
288
444
  - `generateAllOnHandInventoryOfAllResourcesCSV`
289
445
  - `generateAllOnHandInventoryOfAllResources`
290
446
  - `downloadAllInventoryTypesCSV`