Skip to content

Commit

Permalink
Netsuite-adapter: Initial fetch implementation (#762)
Browse files Browse the repository at this point in the history
  • Loading branch information
omrilit committed Mar 8, 2020
1 parent 575c6e0 commit 26a9d96
Show file tree
Hide file tree
Showing 21 changed files with 1,051 additions and 22 deletions.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@salto-io/logging": "0.1.11",
"@salto-io/lowerdash": "0.1.11",
"@salto-io/salesforce-adapter": "0.1.11",
"@salto-io/netsuite-adapter": "0.1.11",
"async": "^3.1.0",
"axios": "^0.19.2",
"chalk": "^2.3.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/core/adapters/creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
import { creator as salesforceAdapterCreator } from '@salto-io/salesforce-adapter'
import { AdapterCreator } from '@salto-io/adapter-api'
import { creator as hubspotAdapterCreator } from '@salto-io/hubspot-adapter'
import { creator as netsuiteAdapterCreator } from '@salto-io/netsuite-adapter'

const adapterCreators: Record<string, AdapterCreator> = {
salesforce: salesforceAdapterCreator,
hubspot: hubspotAdapterCreator,
netsuite: netsuiteAdapterCreator,
}

export default adapterCreators
3 changes: 3 additions & 0 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"paths": {
"jsforce": [
"../../node_modules/jsforce-types"
],
"node-suitetalk": [
"../netsuite-adapter/types/node-suitetalk"
]
},
"lib": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const firstFunc = (): void => {
// eslint-disable-next-line no-console
console.log('Welcome to netsuite adapter')
}
export { default } from './src/adapter'
export { creator } from './src/adapter_creator'
9 changes: 5 additions & 4 deletions packages/netsuite-adapter/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ module.exports = deepMerge(
collectCoverageFrom: [
'!<rootDir>/dist/index.js',
],
// todo: raise the coverageThreshold once more code is implemented
coverageThreshold: {
global: {
branches: 98,
functions: 98,
lines: 98,
statements: 98,
branches: 66,
functions: 93,
lines: 96,
statements: 96,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion packages/netsuite-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@salto-io/logging": "0.1.11",
"@salto-io/lowerdash": "0.1.11",
"lodash": "^4.17.11",
"node-suitetalk": "https://github.com/salto-io/node-suitetalk.git",
"node-suitetalk": "https://github.com/salto-io/node-suitetalk",
"wu": "^2.1.0"
},
"devDependencies": {
Expand Down
68 changes: 68 additions & 0 deletions packages/netsuite-adapter/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2020 Salto Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import _ from 'lodash'
import { Element, InstanceElement, ObjectType, Change } from '@salto-io/adapter-api'
import NetsuiteClient from './client/client'
import { createInstanceElement, Types } from './transformer'
import { METADATA_TYPE } from './constants'


export interface NetsuiteAdapterParams {
client: NetsuiteClient
}

export default class NetsuiteAdapter {
private readonly client: NetsuiteClient

public constructor({ client }: NetsuiteAdapterParams) {
this.client = client
}

/**
* Fetch configuration elements: objects, types and instances for the given Netsuite account.
* Account credentials were given in the constructor.
*/
public async fetch(): Promise<Element[]> {
const objects = Types.customizationObjects
const instances = await this.fetchInstances(Object.values(objects))
return _.flatten([objects, instances] as Element[][])
}

private async fetchInstances(types: ObjectType[]): Promise<InstanceElement[]> {
return _.flatten(await Promise.all(types.map(async type => {
const customRecords = await this.client.listCustomizations(type.annotations[METADATA_TYPE])
return customRecords.map(record => createInstanceElement(record, type))
})))
}

public async add(element: Element): Promise<Element> { // todo: implement
// eslint-disable-next-line no-console
console.log(this.client)
return Promise.resolve(element)
}

public async remove(_element: Element): Promise<void> { // todo: implement
// eslint-disable-next-line no-console
console.log(this.client)
}

public async update(_before: Element, after: Element, _changes: Iterable<Change>):
Promise<Element> { // todo: implement
// eslint-disable-next-line no-console
console.log(this.client)
return Promise.resolve(after)
}
}
55 changes: 55 additions & 0 deletions packages/netsuite-adapter/src/adapter_creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Salto Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AdapterCreator, BuiltinTypes, ElemID, Field, InstanceElement, ObjectType,
} from '@salto-io/adapter-api'
import NetsuiteClient, { Credentials } from './client/client'
import NetsuiteAdapter from './adapter'
import { NETSUITE } from './constants'

const configID = new ElemID(NETSUITE)

const credentialsType = new ObjectType({
elemID: configID,
fields: {
account: new Field(configID, 'account', BuiltinTypes.STRING),
consumerKey: new Field(configID, 'consumerKey', BuiltinTypes.STRING),
consumerSecret: new Field(configID, 'consumerSecret', BuiltinTypes.STRING),
tokenId: new Field(configID, 'tokenId', BuiltinTypes.STRING),
tokenSecret: new Field(configID, 'tokenSecret', BuiltinTypes.STRING),
},
annotationTypes: {},
annotations: {},
})

const netsuiteCredentialsFromCredentials = (credentials: Readonly<InstanceElement>): Credentials =>
credentials.value as Credentials

const clientFromCredentials = (credentials: InstanceElement): NetsuiteClient =>
new NetsuiteClient({
credentials: netsuiteCredentialsFromCredentials(credentials),
})

export const creator: AdapterCreator = {
create: opts => new NetsuiteAdapter({
client: clientFromCredentials(opts.credentials),
}),
validateConfig: config => {
const credentials = netsuiteCredentialsFromCredentials(config)
return NetsuiteClient.validateCredentials(credentials)
},
credentialsType,
}
114 changes: 114 additions & 0 deletions packages/netsuite-adapter/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2020 Salto Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Configuration, Record, Service as Connection } from 'node-suitetalk'
import { decorators } from '@salto-io/lowerdash'
import { ATTRIBUTES } from '../constants'

export type NetsuiteRecord = Record.Types.Record

const API_VERSION = '2019_2'

export type Credentials = {
account: string
consumerKey: string
consumerSecret: string
tokenId: string
tokenSecret: string
}

export type NetsuiteClientOpts = {
credentials: Credentials
connection?: Connection
}

export const realConnection = (credentials: Credentials): Connection => {
const config = new Configuration({
account: credentials.account,
apiVersion: API_VERSION,
accountSpecificUrl: true,
token: {
// eslint-disable-next-line @typescript-eslint/camelcase
consumer_key: credentials.consumerKey,
// eslint-disable-next-line @typescript-eslint/camelcase
consumer_secret: credentials.consumerSecret,
// eslint-disable-next-line @typescript-eslint/camelcase
token_key: credentials.tokenId,
// eslint-disable-next-line @typescript-eslint/camelcase
token_secret: credentials.tokenSecret,
},
wsdlPath: `https://webservices.netsuite.com/wsdl/v${API_VERSION}_0/netsuite.wsdl`,
})
return new Connection(config)
}

export default class NetsuiteClient {
private isLoggedIn = false
private readonly conn: Connection

static validateCredentials(credentials: Credentials): Promise<void> {
return realConnection(credentials).init()
}

constructor({ credentials, connection }: NetsuiteClientOpts) {
this.conn = connection ?? realConnection(credentials)
}

private async ensureLoggedIn(): Promise<void> {
if (!this.isLoggedIn) {
await this.conn.init()
// this.isLoggedIn = true // todo uncomment -> currently each API call requires a new init()
}
}

private static requiresLogin = decorators.wrapMethodWith(
async function withLogin(
this: NetsuiteClient,
originalMethod: decorators.OriginalCall
): Promise<unknown> {
await this.ensureLoggedIn()
return originalMethod.call()
}
)

@NetsuiteClient.requiresLogin
async list(recordReferences: { type: string; internalId: number }[]):
Promise<NetsuiteRecord[]> {
const recordRefs = recordReferences
.map(recordReference => {
const recordRef = new Record.Types.RecordRef()
recordRef.internalId = recordReference.internalId
recordRef.type = recordReference.type
return recordRef
})
const getListResponse = await this.conn.getList(recordRefs)
return getListResponse.readResponseList.readResponse.map(item => item.record)
}

@NetsuiteClient.requiresLogin
private async getCustomizationIds(type: string, includeInactives = true): Promise<number[]> {
const getCustomizationIdResponse = await this.conn.getCustomizationId(type, includeInactives)
return getCustomizationIdResponse.getCustomizationIdResult.customizationRefList
.customizationRef.map(customization => customization[ATTRIBUTES].internalId)
}

@NetsuiteClient.requiresLogin
async listCustomizations(type: string, includeInactives = true): Promise<NetsuiteRecord[]> {
const customizationInternalIds = await this.getCustomizationIds(type, includeInactives)
const customRecordRefs = customizationInternalIds.map(internalId => ({ type, internalId }))
return this.list(customRecordRefs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { firstFunc } from '../src/main'

describe('Test example', () => {
beforeAll(async () => {
firstFunc()
})

it('should pass', () => {
expect(true).toBeTruthy()
})
})
export const NETSUITE = 'netsuite'
export const RECORDS_PATH = 'Records'
export const METADATA_TYPE = 'metadataType'
export const INTERNAL_ID = 'internalId'
export const EXTERNAL_ID = 'externalId'
export const ATTRIBUTES = '$attributes'

0 comments on commit 26a9d96

Please sign in to comment.