release/2024-10-25 - Clean up referenceDocuments
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
13
_reference/Documents/DeploymentChecklist.md
Normal file
13
_reference/Documents/DeploymentChecklist.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Ensure following environment variables are set:
|
||||
|
||||
__S3 Related__
|
||||
AWSAccessKeyId=
|
||||
AWSSecretKey=
|
||||
Bucket=
|
||||
|
||||
__React Based__
|
||||
REACT_APP_GRAPHQL_ENDPOINT
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS
|
||||
|
||||
__MetaData__
|
||||
Region based OpCodes
|
||||
51
_reference/Documents/JSReportSetup.md
Normal file
51
_reference/Documents/JSReportSetup.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# install node.js
|
||||
|
||||
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
||||
|
||||
# you may need to reopen terminal
|
||||
|
||||
nvm install 8.11.3
|
||||
|
||||
mkdir jsreportapp
|
||||
cd jsreportapp
|
||||
npm i -g jsreport-cli
|
||||
jsreport init
|
||||
jsreport configure
|
||||
|
||||
# chrome dependencies
|
||||
|
||||
sudo apt-get install -y libgconf-2-4
|
||||
sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >>
|
||||
/etc/apt/sources.list.d/google.list'
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst
|
||||
--no-install-recommends
|
||||
|
||||
# on ubuntu 20 run also
|
||||
|
||||
sudo apt-get install -y libxtst6 libxss1
|
||||
|
||||
# start jsreport to see it running on port 5488
|
||||
|
||||
jsreport start
|
||||
|
||||
# the next steps are optional to start jsreport on boot
|
||||
|
||||
npm install pm2 -g
|
||||
pm2 start server.js
|
||||
pm2 startup
|
||||
|
||||
# run the output of previous command
|
||||
|
||||
# optionally if you want to use older phantomjs for pdf rendering
|
||||
|
||||
sudo apt-get install -y --no-install-recommends gnupg git curl wget ca-certificates
|
||||
sudo apt-get install -y --no-install-recommends xfonts-base xfonts-75dpi
|
||||
npm i jsreport-phantom-pdf --save --save-exact
|
||||
|
||||
Running on port 80 and 443 without SU
|
||||
$ setcap 'cap_net_bind_service=+ep' /usr/bin/node
|
||||
$ apt-get remove nginx
|
||||
$ cd /path/to/app
|
||||
$ PORT=80 node app
|
||||
578
_reference/Documents/Jest CheatSheet.md
Normal file
578
_reference/Documents/Jest CheatSheet.md
Normal file
@@ -0,0 +1,578 @@
|
||||
<div align="center" markdown="1">
|
||||
|
||||
<img src="https://d3vv6lp55qjaqc.cloudfront.net/items/2D2K45312x0M1q2C0a3P/jest-logo.svg" width="200">
|
||||
|
||||
<h1>Jest cheat sheet</h1>
|
||||
|
||||
</div>
|
||||
|
||||
_I recommend [Mrm](https://github.com/sapegin/mrm-tasks/tree/master/packages/mrm-task-jest)
|
||||
and [jest-codemods](https://github.com/skovhus/jest-codemods) for single-command Jest installation and easy migration
|
||||
from other frameworks._
|
||||
|
||||
<!-- To reformat run: npx prettier --print-width 100 --single-quote --no-semi --prose-wrap never --write Readme.md -->
|
||||
|
||||
<!-- To update TOC run: npx markdown-toc --maxdepth 3 -i Readme.md -->
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Test structure](#test-structure)
|
||||
- [Matchers](#matchers)
|
||||
- [Basic matchers](#basic-matchers)
|
||||
- [Truthiness](#truthiness)
|
||||
- [Numbers](#numbers)
|
||||
- [Strings](#strings)
|
||||
- [Arrays](#arrays)
|
||||
- [Objects](#objects)
|
||||
- [Exceptions](#exceptions)
|
||||
- [Snapshots](#snapshots)
|
||||
- [Mock functions](#mock-functions)
|
||||
- [Misc](#misc)
|
||||
- [Promise matchers (Jest 20+)](#promise-matchers-jest-20)
|
||||
- [Async tests](#async-tests)
|
||||
- [async/await](#asyncawait)
|
||||
- [Promises](#promises)
|
||||
- [done() callback](#done-callback)
|
||||
- [Mocks](#mocks)
|
||||
- [Mock functions](#mock-functions-1)
|
||||
- [Mock modules using jest.mock method](#mock-modules-using-jestmock-method)
|
||||
- [Mock modules using a mock file](#mock-modules-using-a-mock-file)
|
||||
- [Mock object methods](#mock-object-methods)
|
||||
- [Mock getters and setters (Jest 22.1.0+)](#mock-getters-and-setters-jest-2210)
|
||||
- [Mock getters and setters](#mock-getters-and-setters)
|
||||
- [Clearing and restoring mocks](#clearing-and-restoring-mocks)
|
||||
- [Accessing the original module when using mocks](#accessing-the-original-module-when-using-mocks)
|
||||
- [Timer mocks](#timer-mocks)
|
||||
- [Data-driven tests (Jest 23+)](#data-driven-tests-jest-23)
|
||||
- [Skipping tests](#skipping-tests)
|
||||
- [Testing modules with side effects](#testing-modules-with-side-effects)
|
||||
- [Usage with Babel and TypeScript](#usage-with-babel-and-typescript)
|
||||
- [Resources](#resources)
|
||||
- [You may also like](#you-may-also-like)
|
||||
- [Contributing](#contributing)
|
||||
- [Sponsoring](#sponsoring)
|
||||
- [Author and license](#author-and-license)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Test structure
|
||||
|
||||
```js
|
||||
describe('makePoniesPink', () => {
|
||||
beforeAll(() => {
|
||||
/* Runs before all tests */
|
||||
})
|
||||
afterAll(() => {
|
||||
/* Runs after all tests */
|
||||
})
|
||||
beforeEach(() => {
|
||||
/* Runs before each test */
|
||||
})
|
||||
afterEach(() => {
|
||||
/* Runs after each test */
|
||||
})
|
||||
|
||||
test('make each pony pink', () => {
|
||||
const actual = fn(['Alice', 'Bob', 'Eve'])
|
||||
expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve'])
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Matchers
|
||||
|
||||
[Using matchers](http://jestjs.io/docs/en/using-matchers), [matchers docs](https://facebook.github.io/jest/docs/expect.html)
|
||||
|
||||
### Basic matchers
|
||||
|
||||
```js
|
||||
expect(42).toBe(42) // Strict equality (===)
|
||||
expect(42).not.toBe(3) // Strict equality (!==)
|
||||
expect([1, 2]).toEqual([1, 2]) // Deep equality
|
||||
expect({ a: undefined, b: 2 }).toEqual({ b: 2 }) // Deep equality
|
||||
expect({ a: undefined, b: 2 }).not.toStrictEqual({ b: 2 }) // Strict equality (Jest 23+)
|
||||
```
|
||||
|
||||
### Truthiness
|
||||
|
||||
```js
|
||||
// Matches anything that an if statement treats as true (not false, 0, '', null, undefined, NaN)
|
||||
expect('foo').toBeTruthy()
|
||||
// Matches anything that an if statement treats as false (false, 0, '', null, undefined, NaN)
|
||||
expect('').toBeFalsy()
|
||||
// Matches only null
|
||||
expect(null).toBeNull()
|
||||
// Matches only undefined
|
||||
expect(undefined).toBeUndefined()
|
||||
// The opposite of toBeUndefined
|
||||
expect(7).toBeDefined()
|
||||
```
|
||||
|
||||
### Numbers
|
||||
|
||||
```js
|
||||
expect(2).toBeGreaterThan(1)
|
||||
expect(1).toBeGreaterThanOrEqual(1)
|
||||
expect(1).toBeLessThan(2)
|
||||
expect(1).toBeLessThanOrEqual(1)
|
||||
expect(0.2 + 0.1).toBeCloseTo(0.3, 5)
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
```js
|
||||
expect('long string').toMatch('str')
|
||||
expect('coffee').toMatch(/ff/)
|
||||
expect('pizza').not.toMatch('coffee')
|
||||
expect(['pizza', 'coffee']).toEqual([expect.stringContaining('zz'), expect.stringMatching(/ff/)])
|
||||
```
|
||||
|
||||
### Arrays
|
||||
|
||||
```js
|
||||
expect(['Alice', 'Bob', 'Eve']).toHaveLength(3)
|
||||
expect(['Alice', 'Bob', 'Eve']).toContain('Alice')
|
||||
expect([{ a: 1 }, { a: 2 }]).toContainEqual({ a: 1 })
|
||||
expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(['Alice', 'Bob']))
|
||||
```
|
||||
|
||||
### Objects
|
||||
|
||||
```js
|
||||
expect({ a: 1 }).toHaveProperty('a')
|
||||
expect({ a: 1 }).toHaveProperty('a', 1)
|
||||
expect({ a: { b: 1 } }).toHaveProperty('a.b')
|
||||
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 })
|
||||
expect({ a: 1, b: 2 }).toMatchObject({
|
||||
a: expect.any(Number),
|
||||
b: expect.any(Number)
|
||||
})
|
||||
expect([{ a: 1 }, { b: 2 }]).toEqual([
|
||||
expect.objectContaining({ a: expect.any(Number) }),
|
||||
expect.anything()
|
||||
])
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
```js
|
||||
// const fn = () => { throw new Error('Out of cheese!') }
|
||||
expect(fn).toThrow()
|
||||
expect(fn).toThrow('Out of cheese')
|
||||
expect(fn).toThrowErrorMatchingSnapshot()
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Aliases</summary>
|
||||
|
||||
- `toThrowError` → `toThrow`
|
||||
</details>
|
||||
|
||||
### Snapshots
|
||||
|
||||
```js
|
||||
expect(node).toMatchSnapshot()
|
||||
// Jest 23+
|
||||
expect(user).toMatchSnapshot({
|
||||
date: expect.any(Date)
|
||||
})
|
||||
expect(user).toMatchInlineSnapshot()
|
||||
```
|
||||
|
||||
### Mock functions
|
||||
|
||||
```js
|
||||
// const fn = jest.fn()
|
||||
// const fn = jest.fn().mockName('Unicorn') -- named mock, Jest 22+
|
||||
expect(fn).toBeCalled() // Function was called
|
||||
expect(fn).not.toBeCalled() // Function was *not* called
|
||||
expect(fn).toHaveBeenCalledTimes(1) // Function was called only once
|
||||
expect(fn).toBeCalledWith(arg1, arg2) // Any of calls was with these arguments
|
||||
expect(fn).toHaveBeenLastCalledWith(arg1, arg2) // Last call was with these arguments
|
||||
expect(fn).toHaveBeenNthCalledWith(args) // Nth call was with these arguments (Jest 23+)
|
||||
expect(fn).toHaveReturnedTimes(2) // Function was returned without throwing an error (Jest 23+)
|
||||
expect(fn).toHaveReturnedWith(value) // Function returned a value (Jest 23+)
|
||||
expect(fn).toHaveLastReturnedWith(value) // Last function call returned a value (Jest 23+)
|
||||
expect(fn).toHaveNthReturnedWith(value) // Nth function call returned a value (Jest 23+)
|
||||
expect(fn.mock.calls).toEqual([['first', 'call', 'args'], ['second', 'call', 'args']]) // Multiple calls
|
||||
expect(fn.mock.calls[0][0]).toBe(2) // fn.mock.calls[0][0] — the first argument of the first call
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Aliases</summary>
|
||||
|
||||
- `toBeCalled` → `toHaveBeenCalled`
|
||||
- `toBeCalledWith` → `toHaveBeenCalledWith`
|
||||
- `lastCalledWith` → `toHaveBeenLastCalledWith`
|
||||
- `nthCalledWith` → `toHaveBeenNthCalledWith`
|
||||
- `toReturnTimes` → `toHaveReturnedTimes`
|
||||
- `toReturnWith` → `toHaveReturnedWith`
|
||||
- `lastReturnedWith`→ `toHaveLastReturnedWith`
|
||||
- `nthReturnedWith` →`toHaveNthReturnedWith`
|
||||
</details>
|
||||
|
||||
### Misc
|
||||
|
||||
```js
|
||||
expect(new A()).toBeInstanceOf(A)
|
||||
expect(() => {}).toEqual(expect.any(Function))
|
||||
expect('pizza').toEqual(expect.anything())
|
||||
```
|
||||
|
||||
### Promise matchers (Jest 20+)
|
||||
|
||||
```js
|
||||
test('resolve to lemon', () => {
|
||||
expect.assertions(1)
|
||||
// Make sure to add a return statement
|
||||
return expect(Promise.resolve('lemon')).resolves.toBe('lemon')
|
||||
// return expect(Promise.reject('octopus')).rejects.toBeDefined();
|
||||
})
|
||||
```
|
||||
|
||||
Or with async/await:
|
||||
|
||||
```js
|
||||
test('resolve to lemon', async () => {
|
||||
expect.assertions(2)
|
||||
await expect(Promise.resolve('lemon')).resolves.toBe('lemon')
|
||||
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus')
|
||||
})
|
||||
```
|
||||
|
||||
[resolves docs](https://facebook.github.io/jest/docs/en/expect.html#resolves)
|
||||
|
||||
## Async tests
|
||||
|
||||
See [more examples](https://facebook.github.io/jest/docs/en/tutorial-async.html) in Jest docs.
|
||||
|
||||
It’s a good practice to specify a number of expected assertions in async tests, so the test will fail if your assertions
|
||||
weren’t called at all.
|
||||
|
||||
```js
|
||||
test('async test', () => {
|
||||
expect.assertions(3) // Exactly three assertions are called during a test
|
||||
// OR
|
||||
expect.hasAssertions() // At least one assertion is called during a test
|
||||
|
||||
// Your async tests
|
||||
})
|
||||
```
|
||||
|
||||
### async/await
|
||||
|
||||
```js
|
||||
test('async test', async () => {
|
||||
expect.assertions(1)
|
||||
const result = await runAsyncOperation()
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
```
|
||||
|
||||
### Promises
|
||||
|
||||
_Return_ a Promise from your test:
|
||||
|
||||
```js
|
||||
test('async test', () => {
|
||||
expect.assertions(1)
|
||||
return runAsyncOperation().then(result => {
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### done() callback
|
||||
|
||||
Wrap your assertions in try/catch block, otherwise Jest will ignore failures:
|
||||
|
||||
```js
|
||||
test('async test', done => {
|
||||
expect.assertions(1)
|
||||
runAsyncOperation()
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const result = getAsyncOperationResult()
|
||||
expect(result).toBe(true)
|
||||
done()
|
||||
} catch (err) {
|
||||
done.fail(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Mocks
|
||||
|
||||
### Mock functions
|
||||
|
||||
```js
|
||||
test('call the callback', () => {
|
||||
const callback = jest.fn()
|
||||
fn(callback)
|
||||
expect(callback).toBeCalled()
|
||||
expect(callback.mock.calls[0][1].baz).toBe('pizza') // Second argument of the first call
|
||||
})
|
||||
```
|
||||
|
||||
You can also use snapshots:
|
||||
|
||||
```js
|
||||
test('call the callback', () => {
|
||||
const callback = jest.fn().mockName('Unicorn') // mockName is available in Jest 22+
|
||||
fn(callback)
|
||||
expect(callback).toMatchSnapshot()
|
||||
// ->
|
||||
// [MockFunction Unicorn] {
|
||||
// "calls": Array [
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
And pass an implementation to `jest.fn` function:
|
||||
|
||||
```js
|
||||
const callback = jest.fn(() => true)
|
||||
```
|
||||
|
||||
[Mock functions docs](https://facebook.github.io/jest/docs/mock-function-api.html)
|
||||
|
||||
### Mock modules using `jest.mock` method
|
||||
|
||||
```js
|
||||
jest.mock('lodash/memoize', () => a => a) // The original lodash/memoize should exist
|
||||
jest.mock('lodash/memoize', () => a => a, { virtual: true }) // The original lodash/memoize isn’t required
|
||||
```
|
||||
|
||||
[jest.mock docs](https://facebook.github.io/jest/docs/jest-object.html#jestmockmodulename-factory-options)
|
||||
|
||||
> Note: When using `babel-jest`, calls to `jest.mock` will automatically be hoisted to the top of the code block.
|
||||
> Use `jest.doMock` if you want to explicitly avoid this behavior.
|
||||
|
||||
### Mock modules using a mock file
|
||||
|
||||
1. Create a file like `__mocks__/lodash/memoize.js`:
|
||||
|
||||
```js
|
||||
module.exports = a => a
|
||||
```
|
||||
|
||||
2. Add to your test:
|
||||
|
||||
```js
|
||||
jest.mock('lodash/memoize')
|
||||
```
|
||||
|
||||
> Note: When using `babel-jest`, calls to `jest.mock` will automatically be hoisted to the top of the code block.
|
||||
> Use `jest.doMock` if you want to explicitly avoid this behavior.
|
||||
|
||||
[Manual mocks docs](https://facebook.github.io/jest/docs/manual-mocks.html)
|
||||
|
||||
### Mock object methods
|
||||
|
||||
```js
|
||||
const spy = jest.spyOn(console, 'log').mockImplementation(() => {})
|
||||
expect(console.log.mock.calls).toEqual([['dope'], ['nope']])
|
||||
spy.mockRestore()
|
||||
```
|
||||
|
||||
```js
|
||||
const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true }))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
spy.mockRestore()
|
||||
```
|
||||
|
||||
### Mock getters and setters (Jest 22.1.0+)
|
||||
|
||||
```js
|
||||
const location = {}
|
||||
const getTitle = jest.spyOn(location, 'title', 'get').mockImplementation(() => 'pizza')
|
||||
const setTitle = jest.spyOn(location, 'title', 'set').mockImplementation(() => {})
|
||||
```
|
||||
|
||||
### Mock getters and setters
|
||||
|
||||
```js
|
||||
const getTitle = jest.fn(() => 'pizza')
|
||||
const setTitle = jest.fn()
|
||||
const location = {}
|
||||
Object.defineProperty(location, 'title', {
|
||||
get: getTitle,
|
||||
set: setTitle
|
||||
})
|
||||
```
|
||||
|
||||
### Clearing and restoring mocks
|
||||
|
||||
For one mock:
|
||||
|
||||
```js
|
||||
fn.mockClear() // Clears mock usage date (fn.mock.calls, fn.mock.instances)
|
||||
fn.mockReset() // Clears and removes any mocked return values or implementations
|
||||
fn.mockRestore() // Resets and restores the initial implementation
|
||||
```
|
||||
|
||||
> Note: `mockRestore`works only with mocks created by `jest.spyOn`.
|
||||
|
||||
For all mocks:
|
||||
|
||||
```js
|
||||
jest.clearAllMocks()
|
||||
jest.resetAllMocks()
|
||||
jest.restoreAllMocks()
|
||||
```
|
||||
|
||||
### Accessing the original module when using mocks
|
||||
|
||||
```js
|
||||
jest.mock('fs')
|
||||
const fs = require('fs') // Mocked module
|
||||
const fs = require.requireActual('fs') // Original module
|
||||
```
|
||||
|
||||
### Timer mocks
|
||||
|
||||
Write synchronous test for code that uses native timer
|
||||
functions (`setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`).
|
||||
|
||||
```js
|
||||
// Enable fake timers
|
||||
jest.useFakeTimers()
|
||||
|
||||
test('kill the time', () => {
|
||||
const callback = jest.fn()
|
||||
// Run some code that uses setTimeout or setInterval
|
||||
const actual = someFunctionThatUseTimers(callback)
|
||||
// Fast-forward until all timers have been executed
|
||||
jest.runAllTimers()
|
||||
// Check the results synchronously
|
||||
expect(callback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
```
|
||||
|
||||
Use [jest.runOnlyPendingTimers()](https://jestjs.io/docs/en/timer-mocks#run-pending-timers) for special cases.
|
||||
|
||||
Or adjust timers by time with [advanceTimersByTime()](https://jestjs.io/docs/en/timer-mocks#advance-timers-by-time).
|
||||
|
||||
## Data-driven tests (Jest 23+)
|
||||
|
||||
Run the same test with different data:
|
||||
|
||||
```js
|
||||
test.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])('.add(%s, %s)', (a, b, expected) => {
|
||||
expect(a + b).toBe(expected)
|
||||
})
|
||||
```
|
||||
|
||||
Or the same using template literals:
|
||||
|
||||
```js
|
||||
test.each`
|
||||
a | b | expected
|
||||
${1} | ${1} | ${2}
|
||||
${1} | ${2} | ${3}
|
||||
${2} | ${1} | ${3}
|
||||
`('returns $expected when $a is added $b', ({ a, b, expected }) => {
|
||||
expect(a + b).toBe(expected)
|
||||
})
|
||||
```
|
||||
|
||||
Or on `describe` level:
|
||||
|
||||
```js
|
||||
describe.each([['mobile'], ['tablet'], ['desktop']])('checkout flow on %s', (viewport) => {
|
||||
test('displays success page', () => {
|
||||
//
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
[describe.each() docs](https://jestjs.io/docs/en/api.html#describeeachtablename-fn-timeout), [test.each() docs](https://jestjs.io/docs/en/api.html#testeachtablename-fn-timeout),
|
||||
|
||||
## Skipping tests
|
||||
|
||||
Don’t run these tests:
|
||||
|
||||
```js
|
||||
describe.skip('makePoniesPink'...
|
||||
tests.skip('make each pony pink'...
|
||||
```
|
||||
|
||||
Run only these tests:
|
||||
|
||||
```js
|
||||
describe.only('makePoniesPink'...
|
||||
tests.only('make each pony pink'...
|
||||
```
|
||||
|
||||
## Testing modules with side effects
|
||||
|
||||
Node.js and Jest will cache modules you `require`. To test modules with side effects you’ll need to reset the module
|
||||
registry between tests:
|
||||
|
||||
```js
|
||||
const modulePath = '../module-to-test'
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
test('first test', () => {
|
||||
// Prepare conditions for the first test
|
||||
const result = require(modulePath)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('second text', () => {
|
||||
// Prepare conditions for the second test
|
||||
const fn = () => require(modulePath)
|
||||
expect(fn).toThrow()
|
||||
})
|
||||
```
|
||||
|
||||
## Usage with Babel and TypeScript
|
||||
|
||||
Add [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest)
|
||||
or [ts-jest](https://github.com/kulshekhar/ts-jest). Check their docs for installation instructions.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Jest site](https://facebook.github.io/jest/)
|
||||
- [Testing React components with Jest and Enzyme](http://blog.sapegin.me/all/react-jest) by Artem Sapegin
|
||||
- [React Testing Examples](https://react-testing-examples.com/)
|
||||
- [Testing React Applications](https://youtu.be/59Ndb3YkLKA) by Max Stoiber
|
||||
- [Effective Snapshot Testing](https://blog.kentcdodds.com/effective-snapshot-testing-e0d1a2c28eca) by Kent C. Dodds
|
||||
- [Migrating to Jest](https://medium.com/@kentcdodds/migrating-to-jest-881f75366e7e#.pc4s5ut6z) by Kent C. Dodds
|
||||
- [Migrating AVA to Jest](http://browniefed.com/blog/migrating-ava-to-jest/) by Jason Brown
|
||||
- [How to Test React and MobX with Jest](https://semaphoreci.com/community/tutorials/how-to-test-react-and-mobx-with-jest)
|
||||
by Will Stern
|
||||
- [Testing React Intl components with Jest and Enzyme](https://medium.com/@sapegin/testing-react-intl-components-with-jest-and-enzyme-f9d43d9c923e)
|
||||
by Artem Sapegin
|
||||
- [Testing with Jest: 15 Awesome Tips and Tricks](https://medium.com/@stipsan/testing-with-jest-15-awesome-tips-and-tricks-42150ec4c262)
|
||||
by Stian Didriksen
|
||||
- Taking Advantage of Jest Matchers by Ben
|
||||
McCormick: [Part 1](https://benmccormick.org/2017/08/15/jest-matchers-1/), [Part 2](https://benmccormick.org/2017/09/04/jest-matchers-2/)
|
||||
|
||||
---
|
||||
|
||||
## You may also like
|
||||
|
||||
- [Opinionated list of React components](https://github.com/sapegin/react-components)
|
||||
|
||||
## Contributing
|
||||
|
||||
Improvements are welcome! Open an issue or send a pull request.
|
||||
|
||||
## Sponsoring
|
||||
|
||||
This software has been developed with lots of coffee, buy me one more cup to keep it going.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/sapegin" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" height="51" width="217" ></a>
|
||||
|
||||
## Author and license
|
||||
|
||||
[Artem Sapegin](http://sapegin.me/), a frontend engineer at [Omio](https://omio.com/) and the creator
|
||||
of [React Styleguidist](https://react-styleguidist.js.org/). I also write about frontend
|
||||
at [my blog](https://blog.sapegin.me/).
|
||||
|
||||
CC0 1.0 Universal license, see the included [License.md](/License.md) file.
|
||||
59
_reference/Documents/New Hasura Deployment.md
Normal file
59
_reference/Documents/New Hasura Deployment.md
Normal file
@@ -0,0 +1,59 @@
|
||||
Postman Method:
|
||||
POST https://db.imex.online/v1alpha1/pg_dump
|
||||
Set x-hasura-admin-secret header.
|
||||
Body is RAW JSON:
|
||||
|
||||
```
|
||||
{
|
||||
"opts": ["-O", "-x", "--schema-only", "--schema", "public"],
|
||||
"clean_output": true
|
||||
}
|
||||
```
|
||||
|
||||
Save output.
|
||||
Manually export hasura metadata on production.
|
||||
|
||||
Go to new instance.
|
||||
Run SQL
|
||||
|
||||
```
|
||||
CREATE EXTENSION pg_trgm
|
||||
```
|
||||
|
||||
Run SQL from PG Dump
|
||||
Import hasura metadata.
|
||||
|
||||
//Done before IO BETA Release
|
||||
Step 1: Nuke local migrations
|
||||
Delete all the contents of your local migrations directory.
|
||||
|
||||
$ rm migrations/\*
|
||||
Step 2: Reset the migration history on server
|
||||
On the SQL tab of console, execute the following statement:
|
||||
|
||||
TRUNCATE hdb_catalog.schema_migrations;
|
||||
Step 3: Pull the schema and metadata from server
|
||||
Setup fresh migrations by taking the schema and metadata from the server:
|
||||
|
||||
## (available after version alpha45)
|
||||
|
||||
## create migration files (note that this will only export public schema from postgres)
|
||||
|
||||
$ hasura migrate create "init" --from-server
|
||||
|
||||
## note down the version
|
||||
|
||||
## mark the migration as applied on this server
|
||||
|
||||
$ hasura migrate apply --version "<version>" --skip-execution
|
||||
If you are using schemas other than public, use --schema "schema_name" flag to indicate each one of them in the create
|
||||
command. This flag can be used multiple times. See more details about the usage in the docs.
|
||||
|
||||
Step 4: Verify the status
|
||||
Execute the following command to verify status of migration:
|
||||
|
||||
$ hasura migrate status
|
||||
You have brand new migrations now!
|
||||
|
||||
This can also be used to combine (kind of squash) all of your migration files into a single one. You're snapshotting the
|
||||
state of a server and adding it as a new migration.
|
||||
46
_reference/Documents/Responsibility Center Setup.md
Normal file
46
_reference/Documents/Responsibility Center Setup.md
Normal file
@@ -0,0 +1,46 @@
|
||||
Glass Setup
|
||||
|
||||
{"ap":{"name":"Accounts Payable","accountdesc":"Accounts Payable","accountitem":"Accounts Payable","accountname":"
|
||||
Accounts Payable","accountnumber":"20000"},"ar":{"name":"Accounts Receivable","accountdesc":"Accounts Receivable","
|
||||
accountitem":"Accounts Receivable","accountname":"Accounts Receivable","accountnumber":"12000"},"
|
||||
costs":[{"name":"Auto Glass Parts","accountdesc":"Glass","accountitem":"Auto Glass Parts","accountname":"Cost of Goods:Auto Glass Parts","accountnumber":"Glass"},{"name":"Auto Glass Labour","accountdesc":"Auto Glass Labour","accountitem":"Auto Glass Labour","accountname":"Cost of Goods:Auto Glass Labour","accountnumber":"Auto Glass Labour"},{"name":"Flat Glass Parts","accountdesc":"Flat Glass ","accountitem":"Flat Glass Parts","accountname":"Cost of Goods:Flat Glass Parts","accountnumber":"Flat Glass Parts"},{"name":"Flat Glass Labour","accountdesc":"Flat Glass ","accountitem":"Flat Glass Labour","accountname":"Cost of Goods:Flat Glass Labour","accountnumber":"Flat Glass Labour"},{"name":"Home Glass Parts","accountdesc":"Home Glass Parts","accountitem":"Home Glass Parts","accountname":"Cost of Goods:Home Glass Parts","accountnumber":"Home Glass Parts"},{"name":"Home Glass Labour","accountdesc":"Home Glass Labour","accountitem":"Home Glass Labour","accountname":"Cost of Goods:Home Glass Labour","accountnumber":"Home Glass Labour"},{"name":"Misc Parts","accountdesc":"Misc Parts","accountitem":"Misc Parts","accountname":"Cost of Goods:Misc Parts","accountnumber":"Misc Parts"}],"
|
||||
taxes":{"local":{"name":"n","rate":0,"accountdesc":"n","accountitem":"n","accountname":"n","accountnumber":"n"},"
|
||||
state":{"name":"PST","rate":7,"accountdesc":"Ministry of Finance (BC)","accountitem":"PST (BC)","accountname":"PST
|
||||
PAYABLE","accountnumber":"PST PAYABLE"},"federal":{"name":"GST","rate":5,"accountdesc":"Receiver General - GST","
|
||||
accountitem":"GST","accountname":"HST/GST PAYABLE","accountnumber":"HST/GST PAYABLE"}},"refund":{"name":"Refund","
|
||||
accountdesc":"ACCOUNTS RECEIVABLE","accountitem":"BODY SHOP_CUSTPAY","accountname":"ACCOUNTS RECEIVABLE","
|
||||
accountnumber":"ACCOUNTS RECEIVABLE"},"
|
||||
profits":[{"name":"Auto Glass Parts","accountdesc":"Auto Glass","accountitem":"Auto Glass Parts","accountname":"Sales:Auto Glass Parts","accountnumber":"APG"},{"name":"Auto Glass Labour","accountdesc":"Auto Glass Labour","accountitem":"Auto Glass Labour","accountname":"Sales:Auto Glass Labour","accountnumber":"APL"},{"name":"Flat Glass Parts","accountdesc":"Flat Glass","accountitem":"Flat Glass Parts","accountname":"Sales:Flat Glass Parts","accountnumber":"FGP"},{"name":"Flat Glass Labour","accountdesc":"Flat Glass Labour","accountitem":"Flat Glass Labour","accountname":"Sales:Flat Glass Labour","accountnumber":"FGL"},{"name":"Home Glass Parts","accountdesc":"Home Glass Parts","accountitem":"Home Glass Parts","accountname":"Sales:Home Glass Parts","accountnumber":"HGP"},{"name":"Home Glass Labour","accountdesc":"Home Glass Labour","accountitem":"Home Glass Labour","accountname":"Sales:Home Glass Labour","accountnumber":"HGL"},{"name":"Misc Parts","accountdesc":"Misc Parts","accountitem":"Misc Parts","accountname":"Sales:Misc Parts","accountnumber":"MP"}],"
|
||||
defaults":{"costs":{"ATS":"Auto Glass Labour","LAB":"Auto Glass Labour","LAD":"Auto Glass Labour","LAE":"Auto Glass
|
||||
Labour","LAF":"Auto Glass Labour","LAG":"Auto Glass Labour","LAM":"Auto Glass Labour","LAR":"Auto Glass Labour","LAS":"
|
||||
Auto Glass Labour","LAU":"Auto Glass Labour","PAA":"Auto Glass Parts","PAC":"Auto Glass Parts","PAL":"Auto Glass
|
||||
Parts","PAM":"Auto Glass Parts","PAN":"Auto Glass Parts","PAO":"Auto Glass Parts","PAP":"Auto Glass Parts","PAR":"Auto
|
||||
Glass Parts","PAS":"Auto Glass Parts","TOW":"Auto Glass Parts","MAPA":"Auto Glass Labour","MASH":"Auto Glass Labour"},"
|
||||
profits":{"ATS":"Auto Glass Labour","LAB":"Auto Glass Labour","LAD":"Auto Glass Labour","LAE":"Auto Glass Labour","
|
||||
LAF":"Auto Glass Labour","LAG":"Auto Glass Labour","LAM":"Auto Glass Labour","LAR":"Auto Glass Labour","LAS":"Auto Glass
|
||||
Labour","LAU":"Auto Glass Labour","PAA":"Auto Glass Parts","PAC":"Auto Glass Parts","PAL":"Auto Glass Parts","PAM":"Auto
|
||||
Glass Parts","PAN":"Auto Glass Parts","PAO":"Auto Glass Parts","PAP":"Auto Glass Parts","PAR":"Auto Glass Parts","PAS":"
|
||||
Auto Glass Parts","TOW":"Auto Glass Parts","MAPA":"Auto Glass Parts","MASH":"Auto Glass Parts"}},"
|
||||
sales_tax_codes":[{"code":"G","local":false,"state":false,"federal":true,"description":"GST Only"},{"code":"S","state":true,"federal":true,"description":"Standard"},{"code":"E","local":false,"state":false,"federal":false,"description":"Exempt"}]}
|
||||
|
||||
Regular Dev Setup
|
||||
{"ap": {"name": "AP", "accountdesc": "Pay to Others", "accountitem": "A/P", "accountname": "AP Acc#", "accountnumber": "
|
||||
Accounts Payable"}, "ar": {"name": "AR", "accountdesc": "1100", "accountitem": "A/R", "accountname": "ACCOUNTS
|
||||
RECEIVABLE", "accountnumber": "1100"}, "
|
||||
costs": [{"name": "Aftermarket", "accountdesc": "Aftermarket", "accountitem": "Aftermarketi", "accountname": "BODY SHOP COST:PARTS:AFTERMARKET", "accountnumber": "Aftermarket"}, {"name": "ATP", "accountdesc": "ATP", "accountitem": "BODY SHOP_ATP", "accountname": "BODY SHOP COST:ATP", "accountnumber": "ATP"}, {"name": "Body", "accountdesc": "BODY SHOP COST:LABOR", "accountitem": "BODY SHOP_LAB", "accountname": "BODY SHOP COST:LABOR:BODY", "accountnumber": "5001"}, {"name": "Detail", "accountdesc": "Detailing", "accountitem": "Detaili", "accountname": "BODY SHOP COST:LABOR:DETAIL", "accountnumber": "Detail"}, {"name": "Daignostic", "accountdesc": "Daignostic", "accountitem": "Daignostici", "accountname": "Daignostic", "accountnumber": "Daignostic"}, {"name": "Electrical", "accountdesc": "Electrical", "accountitem": "Electricali", "accountname": "Electrical", "accountnumber": "Electrical"}, {"name": "Chrome", "accountdesc": "Chrome", "accountitem": "Chromei", "accountname": "Chrome", "accountnumber": "Chrome"}, {"name": "Frame", "accountdesc": "Frame", "accountitem": "Framei", "accountname": "BODY SHOP COST:LABOR:Frame", "accountnumber": "Frame"}, {"name": "Mechanical", "accountdesc": "Mechanical", "accountitem": "Mechanicali", "accountname": "BODY SHOP COST:LABOR:MECHANICAL", "accountnumber": "Mechanical"}, {"name": "Refinish", "accountdesc": "Refinish", "accountitem": "BODY SHOP_LAR", "accountname": "BODY SHOP COST:LABOR:REFINISH", "accountnumber": "5003"}, {"name": "Structural", "accountdesc": "Structural", "accountitem": "Structurali", "accountname": "Structural", "accountnumber": "Structural"}, {"name": "Existing", "accountdesc": "Existing", "accountitem": "Existingi", "accountname": "Existing", "accountnumber": "Existing"}, {"name": "Glass", "accountdesc": "Glass", "accountitem": "Glassi", "accountname": "BODY SHOP COST:PARTS:Glass", "accountnumber": "Glass"}, {"name": "LKQ", "accountdesc": "LKQ", "accountitem": "LKQi", "accountname": "BODY SHOP COST:PARTS:LKQ", "accountnumber": "LKQ"}, {"name": "OEM", "accountdesc": "OEM", "accountitem": "OEMi", "accountname": "BODY SHOP COST:PARTS:OEM", "accountnumber": "OEM"}, {"name": "OEM Partial", "accountdesc": "Partial", "accountitem": "Partial", "accountname": "BODY SHOP COST:PARTS:OEM Partial", "accountnumber": "OEM Partial"}, {"name": "Re-cored", "accountdesc": "cored", "accountitem": "coredi", "accountname": "Re-cored", "accountnumber": "Re-cored"}, {"name": "Remanufactured", "accountdesc": "Remanufactured", "accountitem": "Remanufacturedi", "accountname": "BODY SHOP COST:PARTS:LKQ", "accountnumber": "Remanufactured"}, {"name": "Other", "accountdesc": "Other", "accountitem": "Otheri", "accountname": "Other", "accountnumber": "Other"}, {"name": "Sublet", "accountdesc": "Sublet to Other", "accountitem": "Subleti", "accountname": "BODY SHOP COST:SUBLET", "accountnumber": "Sublet"}, {"name": "Towing", "accountdesc": "Towingd", "accountitem": "Towingi", "accountname": "BODY SHOP COST:TOWING", "accountnumber": "Towing"}, {"name": "Paint Cost", "accountdesc": "Paint Material by Labor", "accountitem": "BODY SHOP_MAPA", "accountname": "BODY SHOP COST:PARTS:Materials", "accountnumber": "paint mat"}, {"name": "Shop Cost", "accountdesc": "Shop Materials by Labor", "accountitem": "BODY SHOP_MASH", "accountname": "BODY SHOP COST:PARTS:Materials", "accountnumber": "shop"}], "
|
||||
taxes": {"local": {"name": "n", "rate": 0, "accountdesc": "n", "accountitem": "n", "accountname": "n", "
|
||||
accountnumber": "n"}, "state": {"name": "PST", "rate": 7, "accountdesc": "Ministry of Finance (BC)", "accountitem": "PST
|
||||
On Sales", "accountname": "PST Payable", "accountnumber": "2220"}, "federal": {"name": "GST", "rate": 5, "
|
||||
accountdesc": "Receiver General - GST", "accountitem": "GST On Sales", "accountname": "GST DUE-NET:GST Collected", "
|
||||
accountnumber": "2200a"}}, "refund": {"name": "Refund", "accountdesc": "1100", "accountitem": "BODY SHOP_CUSTPAY", "
|
||||
accountname": "ACCOUNTS RECEIVABLE", "accountnumber": "1100"}, "
|
||||
profits": [{"name": "Aftermarket", "accountdesc": "Aftermarket", "accountitem": "BODY SHOP_PAA", "accountname": "Aftermarket", "accountnumber": "Aftermarket"}, {"name": "ATP", "accountdesc": "ATP", "accountitem": "BODY SHOP_ATP", "accountname": "ATP", "accountnumber": "ATP"}, {"name": "Body", "accountdesc": "BODY SHOP SALESLABOR:BODY", "accountitem": "BODY SHOP_LAB", "accountname": "BODY SHOP SALES:LABOR:BODY", "accountnumber": "5002"}, {"name": "Detail", "accountdesc": "Detail", "accountitem": "BODY SHOP_DET", "accountname": "Detail", "accountnumber": "Detail"}, {"name": "Daignostic", "accountdesc": "Daignostic", "accountitem": "BODY SHOP_LAD", "accountname": "Daignostic", "accountnumber": "Daignostic"}, {"name": "Electrical", "accountdesc": "Electrical", "accountitem": "BODY SHOP_LAE", "accountname": "Electrical", "accountnumber": "Electrical"}, {"name": "Chrome", "accountdesc": "Chrome", "accountitem": "BODY SHOP_PAC", "accountname": "Chrome", "accountnumber": "Chrome"}, {"name": "Frame", "accountdesc": "Frame", "accountitem": "BODY SHOP_LAF", "accountname": "Frame", "accountnumber": "Frame"}, {"name": "Mechanical", "accountdesc": "Mechanical", "accountitem": "BODY SHOP_LAM", "accountname": "Mechanical", "accountnumber": "Mechanical"}, {"name": "Refinish", "accountdesc": "BODY SHOP SALES:LABOR:REFINISH", "accountitem": "BODY SHOP_LAR", "accountname": "BODY SHOP SALES:LABOR:REFINISH", "accountnumber": "5003"}, {"name": "Structural", "accountdesc": "Structural", "accountitem": "BODY SHOP_LAS", "accountname": "Structural", "accountnumber": "Structural"}, {"name": "Existing", "accountdesc": "Existing", "accountitem": "BODY SHOP_PAE", "accountname": "Existing", "accountnumber": "Existing"}, {"name": "Glass", "accountdesc": "Glass", "accountitem": "BODY SHOP_PAG", "accountname": "Glass", "accountnumber": "Glass"}, {"name": "LKQ", "accountdesc": "LKQ", "accountitem": "BODY SHOP_PAL", "accountname": "BODY SHOP SALES:PARTS:LKQ", "accountnumber": "LKQ"}, {"name": "OEM", "accountdesc": "BODY SHOP SALES:PARTS:OEM", "accountitem": "BODY SHOP_PAN", "accountname": "BODY SHOP SALES:PARTS:OEM", "accountnumber": "OEM"}, {"name": "OEM Partial", "accountdesc": "Partial", "accountitem": "BODY SHOP_PAP", "accountname": "OEM Partial", "accountnumber": "OEM Partial"}, {"name": "Re-cored", "accountdesc": "cored", "accountitem": "BODY SHOP_PAO", "accountname": "Re-cored", "accountnumber": "Re-cored"}, {"name": "Remanufactured", "accountdesc": "Remanufactured", "accountitem": "BODY SHOP_PAO", "accountname": "Remanufactured", "accountnumber": "Remanufactured"}, {"name": "Other", "accountdesc": "Other", "accountitem": "BODY SHOP_PAO", "accountname": "Other", "accountnumber": "Other"}, {"name": "Sublet", "accountdesc": "Sublet", "accountitem": "BODY SHOP_PAS", "accountname": "Sublet", "accountnumber": "Sublet"}, {"name": "Towing", "accountdesc": "Towing", "accountitem": "BODY SHOP_TOW", "accountname": "Towing", "accountnumber": "Towing"}, {"name": "Paint Profit", "accountdesc": "Paint Material Costs by Labor", "accountitem": "BODY SHOP_MAPA", "accountname": "paint", "accountnumber": "paint"}, {"name": "Shop Profit", "accountdesc": "Shop Material Costs by Labor", "accountitem": "BODY SHOP_MASH", "accountname": "shop", "accountnumber": "shop"}], "
|
||||
defaults": {"costs": {"ATS": "ATP", "LAB": "Body", "LAD": "Daignostic", "LAE": "Electrical", "LAF": "Frame", "LAG": "
|
||||
Glass", "LAM": "Mechanical", "LAR": "Refinish", "LAS": "Structural", "LAU": "Detail", "PAA": "Aftermarket", "PAC": "
|
||||
Chrome", "PAL": "LKQ", "PAM": "Remanufactured", "PAN": "OEM", "PAO": "Other", "PAP": "OEM Partial", "PAR": "Re-cored", "
|
||||
PAS": "Sublet", "TOW": "Towing", "MAPA": "Paint Cost", "MASH": "Shop Cost"}, "profits": {"ATS": "ATP", "LAB": "Body", "
|
||||
LAD": "Daignostic", "LAE": "Electrical", "LAF": "Frame", "LAG": "Glass", "LAM": "Mechanical", "LAR": "Refinish", "
|
||||
LAS": "Structural", "LAU": "Detail", "PAA": "Aftermarket", "PAC": "Chrome", "PAL": "LKQ", "PAM": "Remanufactured", "
|
||||
PAN": "OEM", "PAO": "Other", "PAP": "OEM Partial", "PAR": "Re-cored", "PAS": "Sublet", "TOW": "Towing", "MAPA": "Paint
|
||||
Profit", "MASH": "Shop Profit"}}, "
|
||||
sales_tax_codes": [{"code": "G", "local": false, "state": false, "federal": true, "description": "GST Only"}, {"code": "S", "state": true, "federal": true, "description": "Both"}, {"code": "E", "local": false, "state": false, "federal": false, "description": "Exempt"}]}
|
||||
52
_reference/Documents/SampleMetadata.md
Normal file
52
_reference/Documents/SampleMetadata.md
Normal file
@@ -0,0 +1,52 @@
|
||||
csiqueestions:
|
||||
[{"name": "mailing", "type": "checkbox", "label": "Opt into our mailing list?", "required": false}, {"max": 5, "min": 0, "name": "slider", "type": "slider", "label": "Slide me.", "required": false}, {"name": "feedback", "type": "textarea", "label": "Do you have any general feedback you would like to share?", "required": false}, {"name": "overall", "type": "rate", "label": "How would you rate your overall experience with us?", "required": true}]
|
||||
|
||||
md_ro_statuses
|
||||
{"
|
||||
statuses": ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint", "Reassembly", "Sublet", "Detail", "Completed", "Delivered", "Invoiced", "Exported"], "
|
||||
open_statuses": ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint", "Reassembly", "Sublet", "Detail"], "
|
||||
default_arrived": "Arrived", "default_exported": "Exported", "default_imported": "Open", "default_invoiced": "
|
||||
Invoiced", "default_completed": "Completed", "default_delivered": "Delivered", "default_scheduled": "Scheduled", "
|
||||
production_statuses": ["Repair Plan", "Parts", "Body", "Prep", "Paint", "Reassembly", "Sublet", "Detail", "Completed"]}
|
||||
|
||||
md_order_status
|
||||
{"statuses": ["Ordered", "Received", "Canceled", "Backordered", "Returned"], "default_bo": "Backordered", "
|
||||
default_ordered": "Ordered", "default_canceled": "Canceled", "default_received": "Received", "default_returned": "
|
||||
Returned"}
|
||||
|
||||
responsibilitycenters
|
||||
{"ap": {"name": "AP", "accountdesc": "Pay to Others", "accountitem": "A/P", "accountname": "AP Acc#", "accountnumber": "
|
||||
Accounts Payable"}, "ar": {"name": "AR", "accountdesc": "1100", "accountitem": "A/R", "accountname": "ACCOUNTS
|
||||
RECEIVABLE", "accountnumber": "1100"}, "
|
||||
costs": [{"name": "Aftermarket", "accountdesc": "Aftermarketd", "accountitem": "Aftermarketi", "accountname": "BODY SHOP COST:PARTS:AFTERMARKET", "accountnumber": "Aftermarket"}, {"name": "ATP", "accountdesc": "ATPd", "accountitem": "BODY SHOP_ATP", "accountname": "BODY SHOP COST:ATP", "accountnumber": "ATP"}, {"name": "Body", "accountdesc": "BODY SHOP COST:LABOR", "accountitem": "BODY SHOP_LAB", "accountname": "BODY SHOP COST:LABOR:BODY", "accountnumber": "5001"}, {"name": "Detail", "accountdesc": "Detaild", "accountitem": "Detaili", "accountname": "BODY SHOP COST:LABOR:DETAIL", "accountnumber": "Detail"}, {"name": "Daignostic", "accountdesc": "Daignosticd", "accountitem": "Daignostici", "accountname": "Daignostic", "accountnumber": "Daignostic"}, {"name": "Electrical", "accountdesc": "Electricald", "accountitem": "Electricali", "accountname": "Electrical", "accountnumber": "Electrical"}, {"name": "Chrome", "accountdesc": "Chromed", "accountitem": "Chromei", "accountname": "Chrome", "accountnumber": "Chrome"}, {"name": "Frame", "accountdesc": "Framed", "accountitem": "Framei", "accountname": "BODY SHOP COST:LABOR:Frame", "accountnumber": "Frame"}, {"name": "Mechanical", "accountdesc": "Mechanicald", "accountitem": "Mechanicali", "accountname": "BODY SHOP COST:LABOR:MECHANICAL", "accountnumber": "Mechanical"}, {"name": "Refinish", "accountdesc": "Refinishd", "accountitem": "BODY SHOP_LAR", "accountname": "BODY SHOP COST:LABOR:REFINISH", "accountnumber": "5003"}, {"name": "Structural", "accountdesc": "Structurald", "accountitem": "Structurali", "accountname": "Structural", "accountnumber": "Structural"}, {"name": "Existing", "accountdesc": "Existingd", "accountitem": "Existingi", "accountname": "Existing", "accountnumber": "Existing"}, {"name": "Glass", "accountdesc": "Glassd", "accountitem": "Glassi", "accountname": "BODY SHOP COST:PARTS:Glass", "accountnumber": "Glass"}, {"name": "LKQ", "accountdesc": "LKQd", "accountitem": "LKQi", "accountname": "BODY SHOP COST:PARTS:LKQ", "accountnumber": "LKQ"}, {"name": "OEM", "accountdesc": "OEMd", "accountitem": "OEMi", "accountname": "BODY SHOP COST:PARTS:OEM", "accountnumber": "OEM"}, {"name": "OEM Partial", "accountdesc": "Partiald", "accountitem": "Partial", "accountname": "BODY SHOP COST:PARTS:OEM Partial", "accountnumber": "OEM Partial"}, {"name": "Re-cored", "accountdesc": "coredd", "accountitem": "coredi", "accountname": "Re-cored", "accountnumber": "Re-cored"}, {"name": "Remanufactured", "accountdesc": "Remanufacturedd", "accountitem": "Remanufacturedi", "accountname": "Remanufactured", "accountnumber": "Remanufactured"}, {"name": "Other", "accountdesc": "Otherd", "accountitem": "Otheri", "accountname": "Other", "accountnumber": "Other"}, {"name": "Sublet", "accountdesc": "Subletd", "accountitem": "Subleti", "accountname": "BODY SHOP COST:SUBLET", "accountnumber": "Sublet"}, {"name": "Towing", "accountdesc": "Towingd", "accountitem": "Towingi", "accountname": "BODY SHOP COST:TOWING", "accountnumber": "Towing"}, {"name": "Paint Mat", "accountdesc": "matd", "accountitem": "mati", "accountname": "BODY SHOP COST:PARTS:Materials", "accountnumber": "paint mat"}, {"name": "shop mat", "accountdesc": "shopd", "accountitem": "shopi", "accountname": "BODY SHOP COST:PARTS:Materials", "accountnumber": "shop"}], "
|
||||
taxes": {"local": {"name": "n", "rate": 0, "accountdesc": "n", "accountitem": "n", "accountname": "n", "
|
||||
accountnumber": "n"}, "state": {"name": "PST", "rate": 7, "accountdesc": "Ministry of Finance (BC)", "accountitem": "PST
|
||||
On Sales", "accountname": "PST Payable", "accountnumber": "2220"}, "federal": {"name": "GST", "rate": 5, "
|
||||
accountdesc": "Receiver General - GST", "accountitem": "GST On Sales", "accountname": "GST DUE-NET:GST Collected", "
|
||||
accountnumber": "2200a"}}, "
|
||||
profits": [{"name": "Aftermarket", "accountdesc": "Aftermarketd", "accountitem": "BODY SHOP_PAA", "accountname": "Aftermarket", "accountnumber": "Aftermarket"}, {"name": "ATP", "accountdesc": "ATPd", "accountitem": "BODY SHOP_ATP", "accountname": "ATP", "accountnumber": "ATP"}, {"name": "Body", "accountdesc": "BODY SHOP SALESLABOR:BODY", "accountitem": "BODY SHOP_LAB", "accountname": "BODY SHOP SALES:LABOR:BODY", "accountnumber": "5002"}, {"name": "Detail", "accountdesc": "Detaild", "accountitem": "BODY SHOP_DET", "accountname": "Detail", "accountnumber": "Detail"}, {"name": "Daignostic", "accountdesc": "Daignosticd", "accountitem": "BODY SHOP_LAD", "accountname": "Daignostic", "accountnumber": "Daignostic"}, {"name": "Electrical", "accountdesc": "Electricald", "accountitem": "BODY SHOP_LAE", "accountname": "Electrical", "accountnumber": "Electrical"}, {"name": "Chrome", "accountdesc": "Chromed", "accountitem": "BODY SHOP_PAC", "accountname": "Chrome", "accountnumber": "Chrome"}, {"name": "Frame", "accountdesc": "Framed", "accountitem": "BODY SHOP_LAF", "accountname": "Frame", "accountnumber": "Frame"}, {"name": "Mechanical", "accountdesc": "Mechanicald", "accountitem": "BODY SHOP_LAM", "accountname": "Mechanical", "accountnumber": "Mechanical"}, {"name": "Refinish", "accountdesc": "BODY SHOP SALES:LABOR:REFINISH", "accountitem": "BODY SHOP_LAR", "accountname": "BODY SHOP SALES:LABOR:REFINISH", "accountnumber": "5003"}, {"name": "Structural", "accountdesc": "Structurald", "accountitem": "BODY SHOP_LAS", "accountname": "Structural", "accountnumber": "Structural"}, {"name": "Existing", "accountdesc": "Existingd", "accountitem": "BODY SHOP_PAE", "accountname": "Existing", "accountnumber": "Existing"}, {"name": "Glass", "accountdesc": "Glassd", "accountitem": "BODY SHOP_PAG", "accountname": "Glass", "accountnumber": "Glass"}, {"name": "LKQ", "accountdesc": "LKQd", "accountitem": "BODY SHOP_PAL", "accountname": "LKQ", "accountnumber": "LKQ"}, {"name": "OEM", "accountdesc": "BODY SHOP SALES:PARTS:OEM", "accountitem": "BODY SHOP_PAN", "accountname": "BODY SHOP SALES:PARTS:OEM", "accountnumber": "OEM"}, {"name": "OEM Partial", "accountdesc": "Partiald", "accountitem": "BODY SHOP_PAP", "accountname": "OEM Partial", "accountnumber": "OEM Partial"}, {"name": "Re-cored", "accountdesc": "coredd", "accountitem": "BODY SHOP_PAO", "accountname": "Re-cored", "accountnumber": "Re-cored"}, {"name": "Remanufactured", "accountdesc": "Remanufacturedd", "accountitem": "BODY SHOP_PAO", "accountname": "Remanufactured", "accountnumber": "Remanufactured"}, {"name": "Other", "accountdesc": "Otherd", "accountitem": "BODY SHOP_PAO", "accountname": "Other", "accountnumber": "Other"}, {"name": "Sublet", "accountdesc": "Subletd", "accountitem": "BODY SHOP_PAS", "accountname": "Sublet", "accountnumber": "Sublet"}, {"name": "Towing", "accountdesc": "Towingd", "accountitem": "BODY SHOP_TOW", "accountname": "Towing", "accountnumber": "Towing"}, {"name": "paint", "accountdesc": "paintd", "accountitem": "BODY SHOP_MAPA", "accountname": "paint", "accountnumber": "paint"}, {"name": "shop", "accountdesc": "shopd", "accountitem": "BODY SHOP_MASH", "accountname": "shop", "accountnumber": "shop"}], "
|
||||
defaults": {"costs": {"ATP": "ATP", "LAB": "Body", "LAD": "Daignostic", "LAE": "Electrical", "LAF": "Frame", "LAG": "
|
||||
Glass", "LAM": "Mechanical", "LAR": "Refinish", "LAS": "Structural", "LAU": "Detail", "PAA": "Aftermarket", "PAC": "
|
||||
Chrome", "PAL": "LKQ", "PAM": "Remanufactured", "PAN": "OEM", "PAO": "Other", "PAP": "OEM Partial", "PAR": "Re-cored", "
|
||||
PAS": "Sublet", "TOW": "Towing", "MAPA": "Paint Mat", "MASH": "shop mat"}, "profits": {"ATP": "ATP", "LAB": "Body", "
|
||||
LAD": "Daignostic", "LAE": "Electrical", "LAF": "Frame", "LAG": "Glass", "LAM": "Mechanical", "LAR": "Refinish", "
|
||||
LAS": "Structural", "LAU": "Detail", "PAA": "Aftermarket", "PAC": "Chrome", "PAL": "LKQ", "PAM": "Remanufactured", "
|
||||
PAN": "OEM", "PAO": "Other", "PAP": "OEM Partial", "PAR": "Re-cored", "PAS": "Sublet", "TOW": "Towing", "MAPA": "
|
||||
paint", "MASH": "shop"}}}
|
||||
|
||||
shoprates: {"rate_atp": 8.68}
|
||||
|
||||
productionconfig: {"
|
||||
columnKeys": [{"key": "alert", "width": 57}, {"key": "ro_number", "width": 65}, {"key": "vehicle", "width": 264.1333465576172}, {"key": "clm_no", "width": 127.39999389648438}, {"key": "ownr", "width": 143}, {"key": "labhrs", "width": 48}, {"key": "larhrs", "width": 53.35003662109375}, {"key": "scheduled_completion", "width": 176}, {"key": "status", "width": 101}, {"key": "note", "width": 179}, {"key": "ct", "width": 74}, {"key": "clm_total"}], "
|
||||
tableState": {"sortedInfo": {"order": "descend", "columnKey": "larhrs"}, "filteredInfo": {}}}
|
||||
|
||||
invoicetaxrates
|
||||
{"local_tax_rate": 0, "state_tax_rate": 7, "federal_tax_rate": 5}
|
||||
|
||||
intakechecklist
|
||||
{"
|
||||
form": [{"name": "item1", "type": "checkbox", "label": "Checklist Item 1", "required": true}, {"name": "item2", "type": "checkbox", "label": "Checklist Item 2", "required": true}, {"name": "item3", "type": "checkbox", "label": "Checklist Item 3", "required": true}, {"name": "item4", "type": "checkbox", "label": "Checklist Item 4", "required": true}], "
|
||||
templates": ["estimate_detail", "estimate_detail2"]}
|
||||
|
||||
ssbucekts
|
||||
[{"id": "express", "lt": 10, "gte": 0, "label": "express", "target": 1}, {"id": "small", "lt": 20, "gte": 10, "label": "small", "target": 2}, {"id": "medium", "lt": 30, "gte": 20, "label": "medium", "target": 3}, {"id": "large", "lt": 40, "gte": 30, "label": "large", "target": 4}, {"id": "heavy", "lt": 60, "gte": 40, "label": "heavy", "target": 5}, {"id": "Insane", "gte": 60, "label": "Insane", "target": 1}]
|
||||
316
_reference/Documents/TeamsSlackInvestigationFinalVersion.md
Normal file
316
_reference/Documents/TeamsSlackInvestigationFinalVersion.md
Normal file
@@ -0,0 +1,316 @@
|
||||
## Microsoft Teams
|
||||
|
||||
Integrating Microsoft Teams into your Node.js backend and React frontend application can enhance communication and workflow efficiency,
|
||||
especially in a body shop management context for the automotive industry. Microsoft Teams provides several ways to integrate its functionalities into your application,
|
||||
including bots, tabs, messaging extensions, webhooks, and connectors. Here's an overview of the options and how to implement them:
|
||||
|
||||
### 0.5 - Share to Teams (TM)
|
||||
|
||||
- https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/share-to-teams-from-web-apps
|
||||
- https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/share-to-teams-from-personal-app-or-tab
|
||||
- https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/device-capabilities/people-picker-capability
|
||||
- (Example of some Teams code Integrations) https://github.com/microsoft/teams-powerapps-app-templates/
|
||||
|
||||
### 1. Microsoft Graph API
|
||||
The Microsoft Graph API is the gateway to data and intelligence in Microsoft 365, including Teams. It allows you to work with Teams data like channels, messages, and more. You can use it to post status changes back to Teams or read data from Teams to affect your site.
|
||||
|
||||
Backend (Node.js): Use the @microsoft/microsoft-graph-client package to make API calls to Teams. You will need to handle authentication with Azure AD, which can be done using the @azure/identity package to get tokens for Graph API requests.
|
||||
|
||||
*Implementation Steps*:
|
||||
|
||||
Register your application in Azure AD to get the client_id and client_secret.
|
||||
|
||||
(Is client_id and client_secret going to be something required for every customer or is it going to be a singleton)
|
||||
Implement OAuth 2.0 authorization flow to obtain access tokens.
|
||||
(This will need to be tracked by association to a bodyshop)
|
||||
|
||||
bodyshop->Channels | People ->Messages
|
||||
|
||||
Use the access token to make requests to the Microsoft Graph API to interact with Teams.
|
||||
|
||||
### 2. Webhooks and Connectors
|
||||
Webhooks allow your application to send notifications to a Teams channel, which is useful for posting status changes. Connectors are a set of predefined webhooks that offer a more integrated experience.
|
||||
|
||||
Setup:
|
||||
In Microsoft Teams, configure an incoming webhook for the channel you want to post messages to.
|
||||
Use the webhook URL to send JSON payloads from your Node.js backend, which can then be displayed in Teams.
|
||||
|
||||
|
||||
### 3. Bots
|
||||
Bots in Teams can interact with users to take commands or post information. You can use the Bot Framework along with the Teams activity handler to create bots that can communicate with your React frontend and Node.js backend.
|
||||
|
||||
Backend (Node.js): Use the botbuilder package to create and manage your bot's interactions with Teams.
|
||||
|
||||
*Implementation Steps*:
|
||||
|
||||
Create a bot registration in Azure Bot Services.
|
||||
Implement the bot logic in your Node.js application using the Bot Framework SDK.
|
||||
Use the Microsoft Bot Framework's TeamsActivityHandler to respond to Teams-specific activities.
|
||||
|
||||
### 4. Tabs
|
||||
Tabs in Teams allow you to integrate web-based content as part of Teams, which can be your React application or specific parts of it. This is particularly useful for creating a seamless experience within Teams.
|
||||
|
||||
*Implementation Steps*:
|
||||
- Create a Teams app manifest that defines your tab and its configuration.
|
||||
- Host your React application or the specific parts you want to embed as a tab.
|
||||
- Use the Teams SDK in your React application to interact with Teams context and APIs.
|
||||
|
||||
*Getting Started*:
|
||||
Microsoft Teams Toolkit for Visual Studio Code: This toolkit simplifies the process of setting up your Teams application, including authentication, configuration, and deployment.
|
||||
Documentation and Samples: Microsoft provides extensive documentation and sample code for developing Teams applications, which can be invaluable for getting started and solving specific challenges.
|
||||
Implementing these features requires a good understanding of both the Microsoft Teams platform and your application's architecture.
|
||||
Start small, perhaps by implementing notifications via webhooks, and gradually add more complex integrations like bots or tabs based on your needs and user feedback.
|
||||
|
||||
### Examples:
|
||||
|
||||
##### Posting Messages to a Teams Channel Using Incoming Webhooks
|
||||
|
||||
1 - Set up an Incoming Webhook in Microsoft Teams:
|
||||
- Go to the Teams channel where you want to post messages.
|
||||
- Click on the three dots (...) next to the channel name and select Connectors.
|
||||
- Search for Incoming Webhook, click Add, and then Configure.
|
||||
- Name your webhook, upload an image if desired, and click Create.
|
||||
- Copy the webhook URL provided.
|
||||
|
||||
(Patrick Note: The clients might have issues with setting up webhooks, it requires a lot of user configuration and our users are not technical).
|
||||
|
||||
2 - Node.js Code to Post a Message:
|
||||
|
||||
Ensure you have axios or any HTTP client library installed in your Node.js project. If not, you can install axios via npm: npm install axios.
|
||||
Use the following code snippet to post a message to the Teams channel:
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
// Replace 'YOUR_WEBHOOK_URL' with your actual webhook URL
|
||||
const webhookUrl = 'YOUR_WEBHOOK_URL';
|
||||
|
||||
const message = {
|
||||
"@type": "MessageCard",
|
||||
"@context": "http://schema.org/extensions",
|
||||
"summary": "Issue 176715375",
|
||||
"themeColor": "0078D7",
|
||||
"title": "Issue opened: \"Push notifications not working\"",
|
||||
"sections": [{
|
||||
"activityTitle": "A new issue was created",
|
||||
"activitySubtitle": "Today, 2:36 PM",
|
||||
"activityImage": "https://example.com/issues/image.png",
|
||||
"facts": [{
|
||||
"name": "Repository:",
|
||||
"value": "Repo name"
|
||||
},
|
||||
{
|
||||
"name": "Issue #:",
|
||||
"value": "176715375"
|
||||
}
|
||||
],
|
||||
"markdown": true
|
||||
}]
|
||||
};
|
||||
|
||||
axios.post(webhookUrl, message)
|
||||
.then(response => console.log('Successfully sent message to Teams channel'))
|
||||
.catch(error => console.error('Error sending message to Teams channel', error));
|
||||
```
|
||||
|
||||
#### Creating a Simple Bot for Teams
|
||||
1 Prerequisites:
|
||||
- Register your bot with the Microsoft Bot Framework and get your bot's App ID and App Password.
|
||||
- Use the Bot Framework SDK for JavaScript.
|
||||
2 Install Dependencies:
|
||||
- You need to install the botbuilder package. Run npm install botbuilder.
|
||||
|
||||
The following example demonstrates a simple bot that echoes back received messages.
|
||||
|
||||
```javascript
|
||||
const { BotFrameworkAdapter, MemoryStorage, ConversationState, TurnContext } = require('botbuilder');
|
||||
|
||||
// Create adapter.
|
||||
// See https://aka.ms/about-bot-adapter to learn more about adapters.
|
||||
const adapter = new BotFrameworkAdapter({
|
||||
appId: process.env.MicrosoftAppId,
|
||||
appPassword: process.env.MicrosoftAppPassword
|
||||
});
|
||||
|
||||
// Create conversation state with in-memory storage provider.
|
||||
const conversationState = new ConversationState(new MemoryStorage());
|
||||
adapter.use(conversationState);
|
||||
|
||||
// Listen for incoming requests.
|
||||
server.post('/api/messages', (req, res) => {
|
||||
adapter.processActivity(req, res, async (context) => {
|
||||
// Echo back what the user said
|
||||
if (context.activity.type === 'message') {
|
||||
await context.sendActivity(`You said '${context.activity.text}'`);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Running Your Bot:
|
||||
- Make sure to expose your bot endpoint (/api/messages in this case) to the internet using a service like ngrok.
|
||||
- Update your bot's messaging endpoint in the Microsoft Bot Framework portal to point to the ngrok URL.
|
||||
|
||||
### Slash Commands (Bot)
|
||||
1 - Create a Microsoft Teams App: You need to develop a Teams app that can interact with users through commands. This involves using the Microsoft Teams Developer Platform and possibly the Bot Framework.
|
||||
2 - Use Bots for Custom Commands: The primary way to introduce custom slash commands in Teams is through bots. Bots can respond to specific commands (or messages) that are input by users. When you create a bot for Teams, you can define custom commands that the bot will recognize and respond to.
|
||||
3 - Developing the Bot: You can develop a bot using the Microsoft Bot Framework. This allows your bot to receive and send messages to a Teams channel or chat. Within your bot's code, you can define what actions to take when it receives specific commands.
|
||||
4 - Register Your Bot with Microsoft Bot Framework: Register your bot with the Microsoft Bot Framework and configure it to work with Microsoft Teams. This step involves getting a Microsoft App ID and password that are necessary for your bot to communicate with the Teams platform.
|
||||
5 - Add Your Bot to Microsoft Teams: Once your bot is developed and registered, you can package your Teams app (which includes the bot) and upload it to Microsoft Teams. This will make your bot available to users within Teams, where they can interact with it using the custom commands you've defined.
|
||||
6 - Handling Slash Commands: In the context of your bot's code, you will need to interpret messages that start with a slash (/) as commands. You can then parse the command text and perform the appropriate actions or respond accordingly.
|
||||
7 - Publish Your App: For broader distribution, you can publish your Teams app to the Teams app store or distribute it within your organization through the Teams admin center.
|
||||
/ <command> <arb> ....
|
||||
(Unrelated to teams but then used in teams for functionality)
|
||||
- A Job has a todo list
|
||||
- a todo item may have an employee
|
||||
- a todo item may have a due date
|
||||
- a todo item may have a priority
|
||||
|
||||
--- Call notes ----
|
||||
- Tasks (TODO List)
|
||||
- EMail reminders
|
||||
|
||||
## Slack
|
||||
|
||||
Integrating Slack into your Node.js backend and React frontend application can significantly streamline communication and operations for your automotive industry body shop management software. Slack offers various integration points including bots, apps, webhooks, and its rich API to facilitate interactions between your application and Slack workspace. Here's how you can leverage these integration points:
|
||||
|
||||
### 1. **Slack Web API**
|
||||
|
||||
The Slack Web API allows you to interact with Slack, enabling functionalities like sending messages, managing channels, and more directly from your application.
|
||||
|
||||
- **Backend (Node.js)**: Utilize the `@slack/web-api` package to make API calls from your Node.js backend. This will be the backbone for actions such as posting status updates to Slack channels or handling commands from Slack that can affect your site.
|
||||
|
||||
- **Implementation Steps**:
|
||||
1. Create a Slack app in your Slack workspace and obtain the API tokens.
|
||||
2. Use the `@slack/web-api` package to authenticate and interact with Slack API endpoints.
|
||||
3. Implement features such as sending messages or processing events from Slack.
|
||||
|
||||
### 2. **Incoming Webhooks**
|
||||
|
||||
For simpler integrations focused on sending notifications to Slack channels, incoming webhooks are straightforward and effective. They allow you to send messages to a specific channel without a full-blown app.
|
||||
|
||||
- **Setup**:
|
||||
1. Create an incoming webhook from the Slack app configuration page.
|
||||
2. Use the webhook URL to send messages from your Node.js backend by making simple HTTP POST requests with your message payload.
|
||||
|
||||
### 3. **Bots**
|
||||
|
||||
Slack bots can facilitate interactive experiences within your Slack workspace, responding to commands, posting notifications, or even pulling data from your site on demand.
|
||||
|
||||
- **Backend (Node.js)**: Leverage the `@slack/bolt` framework, which simplifies creating Slack bots with event handling, messaging, and built-in OAuth support.
|
||||
|
||||
- **Implementation Steps**:
|
||||
1. Create a Slack app and enable bot features.
|
||||
2. Use the `@slack/bolt` package to develop your bot, handling events like messages or commands.
|
||||
3. Deploy your bot and set it to listen to incoming events from Slack.
|
||||
|
||||
### 4. **Slack Block Kit**
|
||||
|
||||
For more engaging and interactive messages, Slack's Block Kit provides a UI framework that allows you to create richly formatted messages, modals, and more.
|
||||
|
||||
- **Frontend (React)** and **Backend (Node.js)**: Utilize Slack's Block Kit to design complex messages with buttons, sections, and interactive components. You can send these payloads through the Web API or from bots to enhance your app's interaction with users.
|
||||
|
||||
### Getting Started
|
||||
|
||||
- **Slack API Documentation and Tools**: Slack's API documentation is comprehensive and includes tutorials, tooling (like Block Kit Builder), and SDK documentation to help you get started.
|
||||
|
||||
- **Testing and Development**: Slack provides a sandbox environment for you to test your app's integrations and interactions without affecting your live workspace.
|
||||
|
||||
To integrate Slack into your application effectively, start by planning out the interactions you need (e.g., notifications, commands, information retrieval) and map these to the appropriate Slack features. Then, incrementally build and test each integration, utilizing Slack's development tools and your existing Node.js and React knowledge to create a seamless experience for your users.
|
||||
|
||||
#### Examples
|
||||
|
||||
1. Incoming Webhooks
|
||||
Incoming Webhooks are a simple way to post messages from your Node.js application into Slack channels. They're perfect for notifying team members about status changes or updates.
|
||||
|
||||
Setup:
|
||||
|
||||
1 - Create a new Slack app in your workspace and enable incoming webhooks.
|
||||
2 - Add a new webhook to your app and choose the channel it will post to.
|
||||
3 - Use the webhook URL to send messages from your backend.
|
||||
|
||||
Node.js Example:
|
||||
|
||||
```javascript
|
||||
|
||||
const axios = require('axios');
|
||||
const webhookUrl = 'YOUR_SLACK_WEBHOOK_URL';
|
||||
|
||||
async function postMessageToSlack(message) {
|
||||
await axios.post(webhookUrl, {
|
||||
text: message, // Your message here
|
||||
});
|
||||
}
|
||||
|
||||
postMessageToSlack('New status update on the automotive project!').catch(console.error);
|
||||
```
|
||||
2. Bots
|
||||
Slack bots can interact with users via messages, respond to commands, and even post updates. They're great for building interactive features.
|
||||
|
||||
Setup:
|
||||
|
||||
1 - Create a Slack app and add the Bot Token Scopes (like chat:write).
|
||||
2 - Install the app to your workspace to get your bot token.
|
||||
3 - Use the @slack/bolt framework for easy bot development in Node.js.
|
||||
|
||||
Node.js Example:
|
||||
|
||||
```javascript
|
||||
const { App } = require('@slack/bolt');
|
||||
|
||||
const app = new App({
|
||||
token: process.env.SLACK_BOT_TOKEN, // Set your bot's access token here
|
||||
signingSecret: process.env.SLACK_SIGNING_SECRET // Set your app's signing secret here
|
||||
});
|
||||
|
||||
app.message('hello', async ({ message, say }) => {
|
||||
await say(`Hey there <@${message.user}>!`);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await app.start(process.env.PORT || 3000);
|
||||
console.log('Slack bot is running!');
|
||||
})();
|
||||
```
|
||||
|
||||
3. Slash Commands
|
||||
Slash Commands allow users to interact with your application directly from Slack by typing commands that start with /.
|
||||
|
||||
Setup:
|
||||
|
||||
1 - Create a Slack app and configure a new Slash Command (e.g., /statusupdate).
|
||||
2 - Point the command request URL to your Node.js backend endpoint.
|
||||
3 - Implement the endpoint to handle the command and respond accordingly.
|
||||
|
||||
Node.js Example:
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
|
||||
app.post('/slack/commands/statusupdate', (req, res) => {
|
||||
const { text, user_name } = req.body; // Command input and user info
|
||||
// Logic to handle the command goes here. For example, update a status or fetch information.
|
||||
res.send(`Status update received: ${text} - from ${user_name}`);
|
||||
});
|
||||
|
||||
app.listen(process.env.PORT || 3000, () => console.log('Server is running'));
|
||||
```
|
||||
|
||||
4. Interactive Components
|
||||
Interactive components like buttons or menus can make your Slack messages more engaging and interactive.
|
||||
|
||||
Setup:
|
||||
1 - Enable Interactivity in your Slack app settings.
|
||||
2 - Implement an endpoint in your Node.js backend to handle interactions.
|
||||
3 - Send messages with interactive components from your backend.
|
||||
|
||||
Implementing these features into your Node.js and React application will enable a more dynamic and integrated experience for users of your body shop management software. Start with the simpler integrations like webhooks and progressively incorporate bots and interactive components as needed.
|
||||
|
||||
ACTION ITEMS:
|
||||
- Slot in tasks, this will be a dependency for things we want to do in the future.
|
||||
- This would involve GUI components
|
||||
- This would involve a backend component
|
||||
64
_reference/Documents/dockerreadme.md
Normal file
64
_reference/Documents/dockerreadme.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
||||
|
||||
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Windows 11**
|
||||
2. **Docker Desktop For Windows (Latest Version)
|
||||
|
||||
# Docker Setup
|
||||
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
||||
`docker-compose up` to launch the backend.
|
||||
|
||||
Things to note:
|
||||
- When installing NPM packages, you will need to rebuild the `node-app` container
|
||||
- Making changes to the server files will restart the `node-app`
|
||||
|
||||
# Local Stack
|
||||
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||
|
||||
# Docker Commands
|
||||
|
||||
## General `docker-compose` Commands:
|
||||
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
||||
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
||||
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
||||
4. Stop containers without removing them: `docker-compose stop`
|
||||
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
||||
6. Force rebuild of containers: `docker-compose build --no-cache`
|
||||
7. View running Containers: `docker-compose ps`
|
||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||
|
||||
## Volume Management Commands
|
||||
1. List Docker volumes: `docker volume ls`
|
||||
2. Remove Unused volumes `docker volume prune`
|
||||
3. Remove specific volumes `docker volume rm <volume-name>`
|
||||
4. Inspect a volume: `docker volume inspect <volume-name>`
|
||||
|
||||
## Container Image Management Commands:
|
||||
1. List running containers: `docker ps`
|
||||
2. List all containers: `docker os -a`
|
||||
3. Remove Stopped containers: `docker container prune`
|
||||
4. Remove a specific container: `docker container rm <container-name>`
|
||||
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
||||
6. Remove all unused images: `docker image prune -a`
|
||||
|
||||
## Network Management Commands:
|
||||
1. List networks: `docker network ls`
|
||||
2. Inspect a specific network: `docker network inspect <network-name>`
|
||||
3. Remove a specific network: `docker network rm <network-name>`
|
||||
4. Remove unused networks: `docker network prune`
|
||||
|
||||
## Debugging and maintenance:
|
||||
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
||||
2. View container resource usage: `docker stats`
|
||||
3. Check Disk space used by Docker: `docker system df`
|
||||
4. Remove all unused Data (Nuclear option): `docker system prune`
|
||||
|
||||
## Specific examples
|
||||
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
||||
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
||||
79
_reference/Documents/dropletSetup.md
Normal file
79
_reference/Documents/dropletSetup.md
Normal file
@@ -0,0 +1,79 @@
|
||||
**Create an SSH key for local computer**
|
||||
|
||||
ssh-keygen -t rsa -C "your_email@example.com"
|
||||
|
||||
Copy the new key to clipboard:
|
||||
|
||||
- Windows: clip < id_rsa.pub
|
||||
- Linux: sudo apt-get install xclip
|
||||
xclip -sel clip < ~/.ssh/id_rsa.pub
|
||||
- Mac: pbcopy < ~/.ssh/id_rsa.pub
|
||||
- Manual Copy: cat ~/.ssh/id_rsa.pub
|
||||
|
||||
Add the SSH key to the drop creation screen.
|
||||
|
||||
1. Create a new user to replace root user
|
||||
1. # adduser imex
|
||||
2. # usermod -aG sudo imex
|
||||
3. # su - imex
|
||||
4. $ mkdir ~/.ssh
|
||||
5. $ chmod 700 ~/.ssh
|
||||
6. $ nano ~/.ssh/authorized_keys
|
||||
7. Add the copied SSH key and save.
|
||||
8. $ chmod 600 ~/.ssh/authorized_keys #Restrict access to authorized keys.
|
||||
2. Setup the Firewall
|
||||
1. $ sudo ufw allow OpenSSH.
|
||||
2. $ sudo ufw enable
|
||||
3. Add Nginx & Configure
|
||||
1. $ sudo apt-get update
|
||||
2. $ sudo apt-get install nginx
|
||||
3. $ sudo ufw allow 'Nginx Full'
|
||||
4. $ sudo ufw app list
|
||||
1. Nginx Full: Opens both port 80 (normal, unencrypted web traffic) and port 443 (TLS/SSL encrypted traffic)
|
||||
2. Nginx Http: Opens only port 80 (normal, unencrypted web traffic)
|
||||
3. Nginx Https: Opens only port 443 (TLS/SSL encrypted traffic)
|
||||
5. Should now be able to go to IP and see nginx responding with a blank page.
|
||||
4. Install NodeJs
|
||||
1. $ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
||||
2. $ sudo apt install nodejs
|
||||
3. $ node --version
|
||||
5. Clone Source Code
|
||||
1. $ git clone git@bitbucket.org:snaptsoft/bodyshop.git //Requires SSH setup.
|
||||
2. $ cd bodyshop && npm install //Install all server dependencies.
|
||||
6. Setup PM2
|
||||
1. $ npm install pm2 -g //Had to be run as root.
|
||||
2. $ pm2 start ecosystem.config.js
|
||||
3. $ pm2 startup ubuntu //Ensure it starts when server does.
|
||||
7. Alter Nginx config
|
||||
1. sudo nano /etc/nginx/sites-available/default
|
||||
2. //Add Appropriate server names to the file. www. and non-www.
|
||||
3. Add the following inside the location of the server block: (Remove the 404 bit.)
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
8. Install Certbot
|
||||
9. $ sudo add-apt-repository ppa:certbot/certbot //Potential issue on ubuntu 20.04
|
||||
10. $ sudo apt-get update
|
||||
11. $ sudo apt install python-certbot-nginx
|
||||
12. $ sudo nano /etc/nginx/sites-available/default
|
||||
13. Find the existing server_name line and replace the underscore with your domain name:
|
||||
...
|
||||
server_name example.com www.example.com;
|
||||
...
|
||||
14. $ sudo nginx -t //Verify syntax.
|
||||
15. $ sudo systemctl reload nginx
|
||||
##AWS INSTRUCTIONS
|
||||
$ sudo snap install core; sudo snap refresh core
|
||||
$ sudo snap install --classic certbot
|
||||
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
16. Generate Certificate
|
||||
17. $ sudo certbot --nginx -d example.com -d www.example.com //Follow prompts.
|
||||
18. $ sudo certbot renew --dry-run //Dry run to test auto renewal.
|
||||
|
||||
ADding Yarn
|
||||
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update && sudo apt-get install yarn
|
||||
16
_reference/Documents/firebase.md
Normal file
16
_reference/Documents/firebase.md
Normal file
@@ -0,0 +1,16 @@
|
||||
1. Create a new project
|
||||
2. Setup sign in methods to be user and email only.
|
||||
3. Update .env to include config.
|
||||
4. Setup the Firebase CLI
|
||||
1. cd to client firebase at server directory.
|
||||
2. ensure all dependencies installed
|
||||
1. $ npm install firebase-functions@latest firebase-admin@latest --save
|
||||
2. $ npm install -g firebase-tools
|
||||
3. $ firebase login //Login as needed.
|
||||
5. Set the current projct
|
||||
1. firebase use <projectname>
|
||||
6. Deploy the function
|
||||
1. $ firebase deploy --only functions
|
||||
7. Add the allowed domains.
|
||||
8. Update server variables including FIREBASE_ADMINSDK_JSON, FIREBASE_DATABASE_URL
|
||||
9. Create the firestore and copy the rules from dev for userinstances.
|
||||
41
_reference/Documents/productionBoardNotes.md
Normal file
41
_reference/Documents/productionBoardNotes.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Production Board Notes:
|
||||
|
||||
## General Notes
|
||||
|
||||
- You can single click the lane footer to collapse/un-collapse the lane
|
||||
- You can double click the lane header to collapse/un-collapse the lane
|
||||
- If you need to scroll horizontally, you can hold shift and use the mouse scroll wheel, or press the mouse scroll wheel while scrolling
|
||||
|
||||
## Board Settings
|
||||
|
||||
#### Layout
|
||||
|
||||
- Board Orientation (Vertical or Horizontal)
|
||||
- This determines the orientation of the card layout on the board.
|
||||
- Horizontal is the default setting, and how the prior board was set up.
|
||||
- Vertical is the new setting and allows lanes to be displayed vertically, with a grid of cards
|
||||
- Card Size (Small, Medium, Large)
|
||||
- This determines the size of the cards on the board.
|
||||
- Small is the default setting, and how the prior board was set up.
|
||||
- Medium and Large are new settings and allow for larger cards to be displayed on the board.
|
||||
- Compact Cards (Tall or Wide)
|
||||
- Formally called 'Compact'
|
||||
- When on, data is displayed on the card vertically
|
||||
- when turned off, some fields may share horizontal space, tightening the card layout
|
||||
- Colored Cards (On or Off)
|
||||
- When on, cards are colored based on the Status color
|
||||
- Kiosk Mode (On or Off)
|
||||
- This should be turned on if the shop is using it on a tablet (Ipad)
|
||||
|
||||
#### Information
|
||||
|
||||
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
||||
|
||||
|
||||
### Statistics
|
||||
|
||||
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
|
||||
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
|
||||
|
||||
### Filters
|
||||
- Allows you to set, and persist filters for estimators and insurance companies
|
||||
189
_reference/Documents/reportFiltersAndSorters.md
Normal file
189
_reference/Documents/reportFiltersAndSorters.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Filters and Sorters
|
||||
|
||||
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
|
||||
modify the graphQL query and provide the user more power over their reports.
|
||||
|
||||
For filters and sorters, valid types include (`type` key in the schema):
|
||||
- string (default)
|
||||
- number
|
||||
- bool or boolean
|
||||
- date
|
||||
|
||||
## Special Notes
|
||||
- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
|
||||
|
||||
## High level Schema Overview
|
||||
|
||||
```javascript
|
||||
const schema = {
|
||||
"filters": [
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||
"type": "number" // Type of field, can be number or string currently
|
||||
},
|
||||
// ... more filters
|
||||
],
|
||||
"sorters": [
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||
"type": "number" // Type of field, can be number or string currently
|
||||
},
|
||||
// ... more sorters
|
||||
],
|
||||
"dates": {
|
||||
// This is not yet implemented and will be added in a future release
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Filters
|
||||
|
||||
Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
|
||||
A note on special notation used in the `name` field.
|
||||
|
||||
## Reflection
|
||||
|
||||
Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "jobs.status",
|
||||
"translation": "jobs.fields.status",
|
||||
"label": "Status",
|
||||
"type": "string",
|
||||
"reflector": {
|
||||
"type": "internal",
|
||||
"name": "special.job_statuses"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
|
||||
|
||||
The following cases are available
|
||||
|
||||
- `special.job_statuses` - This will reflect the statuses of the jobs table `bodyshop.md_ro_statuses.statuses'`
|
||||
- `special.cost_centers` - This will reflect the cost centers `bodyshop.md_responsibility_centers.costs`
|
||||
- `special.categories` - This will reflect the categories `bodyshop.md_categories`
|
||||
- `special.insurance_companies` - This will reflect the insurance companies `bodyshop.md_ins_cos`'
|
||||
- `special.employee_teams` - This will reflect the employee teams `bodyshop.employee_teams`
|
||||
- `special.employees` - This will reflect the employees `bodyshop.employees`
|
||||
- `special.first_names` - This will reflect the first names `bodyshop.employees`
|
||||
- `special.last_names` - This will reflect the last names `bodyshop.employees`
|
||||
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources`
|
||||
- `special.class`- This will reflect the class `bodyshop.md_classes`
|
||||
- `special.lost_sale_reasons` - This will reflect the lost sale reasons `bodyshop.md_lost_sale_reasons`
|
||||
- `special.alt_transports` - This will reflect the alternative transports `bodyshop.appt_alt_transport`
|
||||
- `special.payment_types` - This will reflect the payment types `bodyshop.md_payment_types`
|
||||
- `special.payment_payers` - This is a special case with a key value set of [Customer, Insurance]
|
||||
|
||||
### Path without brackets, multi level
|
||||
|
||||
`"name": "jobs.joblines.mod_lb_hrs",`
|
||||
This will produce a where clause at the `joblines` level of the graphQL query,
|
||||
|
||||
```graphql
|
||||
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||
jobs(
|
||||
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}}
|
||||
) {
|
||||
joblines(
|
||||
order_by: {line_no: asc}
|
||||
where: {removed: {_eq: false}, mod_lb_hrs: {_lt: 3}}
|
||||
) {
|
||||
line_no
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
plate_no
|
||||
ro_number
|
||||
status
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
v_color
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Path with brackets,top level
|
||||
|
||||
`"name": "[jobs].joblines.mod_lb_hrs",`
|
||||
This will produce a where clause at the `jobs` level of the graphQL query.
|
||||
|
||||
```graphql
|
||||
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||
jobs(
|
||||
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}, joblines: {mod_lb_hrs: {_gt: 4}}}
|
||||
) {
|
||||
joblines(
|
||||
order_by: {line_no: asc}
|
||||
where: {removed: {_eq: false}}
|
||||
) {
|
||||
line_no
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
plate_no
|
||||
ro_number
|
||||
status
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
v_color
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Known Caveats
|
||||
|
||||
- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs`
|
||||
is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
|
||||
- The type object must be 'string' or 'number' or 'bool' or 'boolean' or 'date' and is case-sensitive.
|
||||
- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
|
||||
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
|
||||
redundant and could cause issues.
|
||||
- Do not add the ability to filter on things like FK constraints, must like the above example.
|
||||
- *INHERITANCE CAVEAT* If you have a filters file on a parent report that has a child that you do not want the filters inherited from, you must place a blank filters file (valid json so {}) on the child report level. This will than fetch the child filters, which are empty and move along, versus inheriting the parent filters.
|
||||
|
||||
## Sorters
|
||||
|
||||
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting,
|
||||
a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs`
|
||||
would be added at the `joblines` level.
|
||||
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results
|
||||
using the sorters.
|
||||
|
||||
### Default Sorters
|
||||
|
||||
- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
|
||||
- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs",
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1",
|
||||
"label": "mod_lb_hrs_1",
|
||||
"type": "number",
|
||||
"default": {
|
||||
"order": 1,
|
||||
"direction": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
30
_reference/Documents/test api setup.md
Normal file
30
_reference/Documents/test api setup.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Clone Repository for:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "node-webhook-scripts",
|
||||
"version": "1.0.0",
|
||||
"main": "index.jsx",
|
||||
"dependencies": {
|
||||
"express": "^4.16.4"
|
||||
},
|
||||
"license": "SEE LICENCE IN LICENCE.md",
|
||||
"author": {
|
||||
"name": "Alexandre Pénombre",
|
||||
"email": "alexandre.penombre@gmail.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
hooks.js:
|
||||
|
||||
```javascript
|
||||
module.exports = [
|
||||
{
|
||||
path: "/pull",
|
||||
command: "git pull && yarn && pm2 restart 0",
|
||||
cwd: "/home/ubuntu/io/",
|
||||
method: "post",
|
||||
},
|
||||
];
|
||||
```
|
||||
Reference in New Issue
Block a user