Skip to content

Commit

Permalink
FAI-14493 Leverage workday converter for okta users (#1864)
Browse files Browse the repository at this point in the history
  • Loading branch information
ypc-faros authored Dec 21, 2024
1 parent c44fe7e commit 6b4ba65
Show file tree
Hide file tree
Showing 18 changed files with 323 additions and 400 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"title": "Okta",
"type": "object",
"oneOf": [
{
"title": "Configuration",
"type": "object",
"properties": {
"source_type": {
"type": "string",
"const": "Okta",
"order": 0
},
"column_names_mapping": {
"order": 1,
"title": "Column Names Mapping",
"description": "Mapping of column names in Okta profile to employee fields",
"type": "object",
"properties": {
"start_date_column_name": {
"title": "Start Date Column Name",
"description": "Column name for employee start date",
"type": "string",
"default": "startDate",
"order": 0
},
"full_name_column_name": {
"title": "Full Name Column Name",
"description": "Column name for employee full name",
"type": "string",
"default": "displayName",
"order": 1
},
"first_name_column_name": {
"title": "First Name Column Name",
"description": "Column name for employee first name",
"type": "string",
"default": "firstName",
"order": 2
},
"last_name_column_name": {
"title": "Last Name Column Name",
"description": "Column name for employee last name",
"type": "string",
"default": "lastName",
"order": 3
},
"employee_id_column_name": {
"title": "Employee ID Column Name",
"description": "Column name for employee ID",
"type": "string",
"default": "employeeNumber",
"order": 4
},
"manager_name_column_name": {
"title": "Manager Name Column Name",
"description": "Column name for manager name",
"type": "string",
"default": "manager",
"order": 5
},
"manager_id_column_name": {
"title": "Manager ID Column Name",
"description": "Column name for manager ID",
"type": "string",
"default": "managerId",
"order": 6
},
"team_id_column_name": {
"title": "Team ID Column Name",
"description": "Column name for team ID",
"type": "string",
"default": "departmentId",
"order": 7
},
"team_name_column_name": {
"title": "Team Name Column Name",
"description": "Column name for team name",
"type": "string",
"default": "department",
"order": 8
},
"termination_date_column_name": {
"title": "Termination Date Column Name",
"description": "Column name for termination date",
"type": "string",
"default": "endDate",
"order": 9
},
"location_column_name": {
"title": "Location Column Name",
"description": "Column name for location",
"type": "string",
"default": "location",
"order": 10
},
"email_column_name": {
"title": "Email Column Name",
"description": "Column name for email",
"type": "string",
"default": "email",
"order": 11
},
"employee_type_column_name": {
"title": "Employee Type Column Name",
"description": "Column name for employee type",
"type": "string",
"default": "employeeType",
"order": 12
}
}
}
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
"octopus": {
"$ref": "octopus.json"
},
"okta": {
"$ref": "okta.json"
},
"opsgenie": {
"$ref": "opsgenie.json"
},
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface Profile {
email?: string;
userType?: string;
startDate?: string;
[key: string]: any;
}

interface Email {
Expand Down
159 changes: 82 additions & 77 deletions destinations/airbyte-faros-destination/src/converters/okta/users.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,98 @@
import {AirbyteRecord} from 'faros-airbyte-cdk';
import {Utils} from 'faros-js-client';
import {sortBy} from 'lodash';

import {DestinationModel, DestinationRecord} from '../converter';
import {DestinationModel, DestinationRecord, StreamContext} from '../converter';
import {Customreports} from '../workday/customreports';
import {EmployeeRecord} from '../workday/models';
import {OktaConverter} from './common';
import {User} from './models';

export class Users extends OktaConverter {
readonly destinationModels: ReadonlyArray<DestinationModel> = [
'identity_Identity',
'org_Department',
'org_Employee',
];
type ColumnNameMapping = {
start_date_column_name?: string;
full_name_column_name?: string;
first_name_column_name?: string;
last_name_column_name?: string;
employee_id_column_name?: string;
manager_name_column_name?: string;
manager_id_column_name?: string;
team_id_column_name?: string;
team_name_column_name?: string;
termination_date_column_name?: string;
location_column_name?: string;
email_column_name?: string;
employee_type_column_name?: string;
};

private seenDepartments = new Set<string>();
export interface OktaConfig {
column_names_mapping?: ColumnNameMapping;
}

async convert(
record: AirbyteRecord
): Promise<ReadonlyArray<DestinationRecord>> {
const source = this.streamName.source;
const user = record.record.data as User;
const profile = user.profile;
const uid = user.id;
const res: DestinationRecord[] = [];
export class Users extends OktaConverter {
private workdayConverter = new Customreports();
private _config: OktaConfig = undefined;

const fullName = [profile.firstName, profile.middleName, profile.lastName]
.filter((x) => x)
.join(' ');
private initialize(ctx: StreamContext): void {
this._config =
this._config ?? ctx.config.source_specific_configs?.okta ?? {};
}

const joinedAt =
Utils.toDate(profile.startDate) ?? Utils.toDate(user.created) ?? null;
readonly destinationModels: ReadonlyArray<DestinationModel> =
this.workdayConverter.destinationModels;

const departments = sortBy(
Object.entries(profile).filter(([k, v]) =>
k.toLowerCase().includes('department')
),
([k, v]) => k
).map(([k, v]) => v);
async convert(
record: AirbyteRecord,
ctx?: StreamContext
): Promise<ReadonlyArray<DestinationRecord>> {
this.initialize(ctx);
const rec = record.record.data as User;
const profile = rec.profile;

const departmentUid =
(departments.length > 0 ? departments[0] : null) ??
profile.department ??
null;
const startDate =
profile[this._config.column_names_mapping.start_date_column_name];
const fullName =
profile[this._config.column_names_mapping.full_name_column_name];
const firstName =
profile[this._config.column_names_mapping.first_name_column_name];
const lastName =
profile[this._config.column_names_mapping.last_name_column_name];
const employeeId =
profile[this._config.column_names_mapping.employee_id_column_name];
const managerName =
profile[this._config.column_names_mapping.manager_name_column_name];
const managerId =
profile[this._config.column_names_mapping.manager_id_column_name];
const teamId =
profile[this._config.column_names_mapping.team_id_column_name];
const teamName =
profile[this._config.column_names_mapping.team_name_column_name];
const terminationDate =
profile[this._config.column_names_mapping.termination_date_column_name];
const location =
profile[this._config.column_names_mapping.location_column_name];
const email = profile[this._config.column_names_mapping.email_column_name];
const employeeType =
profile[this._config.column_names_mapping.employee_type_column_name];

if (departmentUid && !this.seenDepartments.has(departmentUid)) {
this.seenDepartments.add(departmentUid);
res.push({
model: 'org_Department',
record: {
uid: departmentUid,
name: departmentUid,
description: null,
},
});
}
const asWorkdayRecord: EmployeeRecord = {
Start_Date: startDate,
Full_Name: fullName ?? `${firstName} ${lastName}`,
Employee_ID: employeeId,
Manager_Name: managerName,
Manager_ID: managerId,
Team_ID: teamId,
Team_Name: teamName,
Termination_Date: terminationDate,
Location: location,
Email: email,
Employee_Type: employeeType,
};

res.push(
{
model: 'identity_Identity',
record: {
uid,
firstName: profile.firstName,
lastName: profile.lastName,
fullName,
photoUrl: null,
primaryEmail: profile.email,
emails: [profile.email],
createdAt: null,
updatedAt: null,
},
},
{
model: 'org_Employee',
record: {
uid,
title: profile.title,
level: null,
joinedAt,
terminatedAt: null,
department: departmentUid ? {uid: departmentUid} : null,
identity: {uid, source},
manager: profile.manager ? {uid: profile.manager, source} : null,
reportingChain: null, // TODO: compute reporting chain
location: null, // TODO: lookup location
source,
},
}
);
record.record.data = asWorkdayRecord;
return this.workdayConverter.convert(record, ctx);
}

return res;
async onProcessingComplete(
ctx: StreamContext
): Promise<ReadonlyArray<DestinationRecord>> {
return this.workdayConverter.onProcessingComplete(ctx);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ describe('converter registry', () => {

test('loads a converter with underscore instead of dash', async () => {
const streamNames = [
new StreamName('okta-faros', 'groups'),
new StreamName('okta_faros', 'groups'),
new StreamName('aws-cloudwatch-metrics', 'metrics'),
new StreamName('aws_cloudwatch_metrics', 'metrics'),
];
Expand Down
Loading

0 comments on commit 6b4ba65

Please sign in to comment.