dovico 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dockerignore +1 -0
- data/.gitignore +4 -0
- data/.gitlab-ci.yml +15 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Dockerfile +28 -0
- data/Gemfile +3 -0
- data/Makefile +66 -0
- data/README.md +106 -0
- data/Rakefile +11 -0
- data/bin/console +6 -0
- data/bin/dovico +4 -0
- data/doc/API.md +135 -0
- data/dovico-client.gemspec +39 -0
- data/dovico.yml.example +26 -0
- data/lib/dovico.rb +9 -0
- data/lib/dovico/api_client.rb +67 -0
- data/lib/dovico/app.rb +138 -0
- data/lib/dovico/model/assignment.rb +26 -0
- data/lib/dovico/model/employee.rb +27 -0
- data/lib/dovico/model/project.rb +18 -0
- data/lib/dovico/model/task.rb +6 -0
- data/lib/dovico/model/time_entry.rb +83 -0
- data/lib/dovico/model/time_entry_generator.rb +58 -0
- data/lib/dovico/version.rb +3 -0
- data/spec/helper.rb +24 -0
- data/spec/unit/dovico/api_client_spec.rb +81 -0
- data/spec/unit/dovico/model/assignment_spec.rb +26 -0
- data/spec/unit/dovico/model/employee_spec.rb +44 -0
- data/spec/unit/dovico/model/project_spec.rb +56 -0
- data/spec/unit/dovico/model/task_spec.rb +26 -0
- data/spec/unit/dovico/model/time_entry_generator_spec.rb +86 -0
- data/spec/unit/dovico/model/time_entry_spec.rb +168 -0
- metadata +332 -0
data/spec/helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
require "simplecov-rcov"
|
3
|
+
SimpleCov.formatters = [
|
4
|
+
SimpleCov::Formatter::HTMLFormatter,
|
5
|
+
SimpleCov::Formatter::RcovFormatter
|
6
|
+
]
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter "db/"
|
9
|
+
add_filter "spec/"
|
10
|
+
add_group "Dovico", "lib/dovico"
|
11
|
+
end
|
12
|
+
require "rspec"
|
13
|
+
require "rspec/its"
|
14
|
+
require "timecop"
|
15
|
+
require "rack/test"
|
16
|
+
require 'webmock/rspec'
|
17
|
+
|
18
|
+
require 'pry'
|
19
|
+
|
20
|
+
# Load libs
|
21
|
+
require "dovico"
|
22
|
+
|
23
|
+
Timecop.safe_mode = true
|
24
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::ApiClient do
|
5
|
+
let(:client_token) { "aaaaa-client-token-aaaaa" }
|
6
|
+
let(:user_token) { "zzzzz-user-token-zzzzz" }
|
7
|
+
let(:response_body) do
|
8
|
+
{
|
9
|
+
"Stubbed": "Response"
|
10
|
+
}.to_json
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
Dovico::ApiClient.initialize!(client_token, user_token)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".get" do
|
18
|
+
context 'with a succcessful response' do
|
19
|
+
it "passes the correct headers to the request and parses the JSON response" do
|
20
|
+
stub_request(
|
21
|
+
:get,
|
22
|
+
"#{Dovico::ApiClient::API_URL}TimeEntries?filter=1234%205678&version=#{Dovico::ApiClient::API_VERSION}"
|
23
|
+
)
|
24
|
+
.with(
|
25
|
+
headers: {
|
26
|
+
'Accept': 'application/json',
|
27
|
+
'Content-Type': 'application/json',
|
28
|
+
'Authorization': "WRAP access_token=\"client=#{client_token}&user_token=#{user_token}\"",
|
29
|
+
}
|
30
|
+
)
|
31
|
+
.to_return(body: response_body)
|
32
|
+
|
33
|
+
response = Dovico::ApiClient.get(
|
34
|
+
'TimeEntries',
|
35
|
+
params: {
|
36
|
+
filter: '1234 5678'
|
37
|
+
}
|
38
|
+
)
|
39
|
+
|
40
|
+
expect(response).to eq({"Stubbed": "Response"}.stringify_keys)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with a succcessful response' do
|
45
|
+
it "raises an error if the API response is not 200 OK" do
|
46
|
+
stub_request(:get, "#{Dovico::ApiClient::API_URL}TimeEntries?version=#{Dovico::ApiClient::API_VERSION}")
|
47
|
+
.to_return(
|
48
|
+
status: 503,
|
49
|
+
body: response_body
|
50
|
+
)
|
51
|
+
|
52
|
+
expect { Dovico::ApiClient.get('TimeEntries') }.to raise_error 'Error during HTTP request'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe ".post" do
|
58
|
+
it 'posts the body with a POST request' do
|
59
|
+
stub_request(:post,
|
60
|
+
"#{Dovico::ApiClient::API_URL}TimeEntries?version=#{Dovico::ApiClient::API_VERSION}"
|
61
|
+
)
|
62
|
+
.with(
|
63
|
+
body: '{"Fake":"Object"}',
|
64
|
+
)
|
65
|
+
.to_return(body: response_body)
|
66
|
+
|
67
|
+
Dovico::ApiClient.post('TimeEntries', body: {"Fake": "Object"}.to_json)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ".put" do
|
72
|
+
it 'perform a PUT request' do
|
73
|
+
stub_request(:put, "#{Dovico::ApiClient::API_URL}TimeEntries?version=#{Dovico::ApiClient::API_VERSION}")
|
74
|
+
.to_return(body: response_body)
|
75
|
+
|
76
|
+
response = Dovico::ApiClient.put('TimeEntries')
|
77
|
+
expect(response).to eq({"Stubbed": "Response"}.stringify_keys)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::Assignment do
|
5
|
+
let(:assignment_api_hash) do
|
6
|
+
{
|
7
|
+
"ItemID": "123",
|
8
|
+
"AssignmentID": "T456",
|
9
|
+
"Name": "Dovico API Client",
|
10
|
+
"StartDate": "2017-01-01",
|
11
|
+
"FinishDate": "2017-12-31",
|
12
|
+
}.stringify_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".parse" do
|
16
|
+
it "parses the API hash" do
|
17
|
+
assignment = Dovico::Assignment.parse(assignment_api_hash)
|
18
|
+
|
19
|
+
expect(assignment).to be_an(Dovico::Assignment)
|
20
|
+
expect(assignment.id).to eq('123')
|
21
|
+
expect(assignment.assignement_id).to eq('T456')
|
22
|
+
expect(assignment.name).to eq('Dovico API Client')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::Employee do
|
5
|
+
let(:employee_api_hash) do
|
6
|
+
{
|
7
|
+
"ID": "123",
|
8
|
+
"FirstName": "James",
|
9
|
+
"LastName": "Bond",
|
10
|
+
}.stringify_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".parse" do
|
14
|
+
it "parses the hash" do
|
15
|
+
employee = Dovico::Employee.parse(employee_api_hash)
|
16
|
+
|
17
|
+
expect(employee).to be_an(Dovico::Employee)
|
18
|
+
expect(employee.id).to eq('123')
|
19
|
+
expect(employee.first_name).to eq('James')
|
20
|
+
expect(employee.last_name).to eq('Bond')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".myself" do
|
25
|
+
let(:employee_me_answer) do
|
26
|
+
{
|
27
|
+
"Employees": [ employee_api_hash ]
|
28
|
+
}.stringify_keys
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
allow(ApiClient).to receive(:get).and_return(employee_me_answer)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'calls the API and return an Employee object' do
|
36
|
+
myself = Dovico::Employee.myself
|
37
|
+
|
38
|
+
expect(ApiClient).to have_received(:get).with("#{Dovico::Employee::URL_PATH}/Me")
|
39
|
+
expect(myself).to be_an(Dovico::Employee)
|
40
|
+
expect(myself.id).to eq('123')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::Project do
|
5
|
+
let(:project_api_hash) do
|
6
|
+
{
|
7
|
+
"ItemID": "123",
|
8
|
+
"AssignmentID": "T456",
|
9
|
+
"Name": "Project Dovico API Client",
|
10
|
+
"StartDate": "2017-01-01",
|
11
|
+
"FinishDate": "2017-12-31",
|
12
|
+
}.stringify_keys
|
13
|
+
end
|
14
|
+
let(:projects_api_hash) do
|
15
|
+
{
|
16
|
+
"Assignments": [project_api_hash]
|
17
|
+
}.stringify_keys
|
18
|
+
end
|
19
|
+
let(:task_api_hash) do
|
20
|
+
{
|
21
|
+
"ItemID": "789",
|
22
|
+
"AssignmentID": "E456",
|
23
|
+
"Name": "Task write specs",
|
24
|
+
"StartDate": "2016-10-25",
|
25
|
+
"FinishDate": "2018-05-01",
|
26
|
+
}.stringify_keys
|
27
|
+
end
|
28
|
+
let(:tasks_api_hash) do
|
29
|
+
{
|
30
|
+
"Assignments": [task_api_hash]
|
31
|
+
}.stringify_keys
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".all" do
|
35
|
+
before do
|
36
|
+
allow(ApiClient).to receive(:get).with(Dovico::Project::URL_PATH).and_return(projects_api_hash)
|
37
|
+
allow(ApiClient).to receive(:get).with("#{Dovico::Project::URL_PATH}T456").and_return(tasks_api_hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "lists all the assignements" do
|
41
|
+
projects = Dovico::Project.all
|
42
|
+
|
43
|
+
expect(projects.count).to eq(1)
|
44
|
+
project = projects.first
|
45
|
+
expect(project).to be_an(Dovico::Project)
|
46
|
+
expect(project.id).to eq('123')
|
47
|
+
expect(project.name).to eq('Project Dovico API Client')
|
48
|
+
|
49
|
+
expect(project.tasks.count).to eq(1)
|
50
|
+
task = project.tasks.first
|
51
|
+
expect(task.id).to eq('789')
|
52
|
+
expect(task.name).to eq('Task write specs')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::Task do
|
5
|
+
let(:task_api_hash) do
|
6
|
+
{
|
7
|
+
"ItemID": "123",
|
8
|
+
"AssignmentID": "E456",
|
9
|
+
"Name": "Dovico API Client",
|
10
|
+
"StartDate": "2017-01-01",
|
11
|
+
"FinishDate": "2017-12-31",
|
12
|
+
}.stringify_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".parse" do
|
16
|
+
it "parses the API hash" do
|
17
|
+
task = Dovico::Task.parse(task_api_hash)
|
18
|
+
|
19
|
+
expect(task).to be_an(Dovico::Task)
|
20
|
+
expect(task.id).to eq('123')
|
21
|
+
expect(task.assignement_id).to eq('E456')
|
22
|
+
expect(task.name).to eq('Dovico API Client')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::TimeEntryGenerator do
|
5
|
+
subject do
|
6
|
+
Dovico::TimeEntryGenerator.new(
|
7
|
+
employee_id: employee_id,
|
8
|
+
assignments: assignments_config
|
9
|
+
)
|
10
|
+
end
|
11
|
+
let(:employee_id) { "999" }
|
12
|
+
let(:assignment_1) do
|
13
|
+
{
|
14
|
+
"project_id": "1234",
|
15
|
+
"task_id": "456",
|
16
|
+
"hours": 4,
|
17
|
+
}.stringify_keys
|
18
|
+
end
|
19
|
+
let(:assignment_2) do
|
20
|
+
{
|
21
|
+
"project_id": "6789",
|
22
|
+
"task_id": "999",
|
23
|
+
"hours": 3,
|
24
|
+
}.stringify_keys
|
25
|
+
end
|
26
|
+
let(:assignments_config) do
|
27
|
+
{
|
28
|
+
"default_day": [ assignment_1, assignment_2 ]
|
29
|
+
}.stringify_keys
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#generate" do
|
33
|
+
let(:start_date) { Date.parse('2017-01-02') }
|
34
|
+
let(:end_date) { Date.parse('2017-01-06') }
|
35
|
+
|
36
|
+
context 'with minimal configuration' do
|
37
|
+
it 'creates TimeEntries object for the date range' do
|
38
|
+
time_entries = subject.generate(start_date, end_date)
|
39
|
+
|
40
|
+
# 5 days * 2 time entries
|
41
|
+
expect(time_entries.count).to eq(10)
|
42
|
+
first_time_entry = time_entries.first
|
43
|
+
last_time_entry = time_entries.last
|
44
|
+
|
45
|
+
expect(first_time_entry.id).to be_nil
|
46
|
+
expect(first_time_entry.employee_id).to eq(employee_id)
|
47
|
+
expect(first_time_entry.project_id).to eq('1234')
|
48
|
+
expect(first_time_entry.task_id).to eq('456')
|
49
|
+
expect(first_time_entry.date).to eq('2017-01-02')
|
50
|
+
expect(first_time_entry.start_time).to eq('0900')
|
51
|
+
expect(first_time_entry.stop_time).to eq('1300')
|
52
|
+
expect(first_time_entry.total_hours).to eq(4)
|
53
|
+
|
54
|
+
expect(first_time_entry.employee_id).to eq(employee_id)
|
55
|
+
expect(last_time_entry.project_id).to eq('6789')
|
56
|
+
expect(last_time_entry.task_id).to eq('999')
|
57
|
+
expect(last_time_entry.date).to eq('2017-01-06')
|
58
|
+
expect(last_time_entry.start_time).to eq('1300')
|
59
|
+
expect(last_time_entry.stop_time).to eq('1600')
|
60
|
+
expect(last_time_entry.total_hours).to eq(3)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with specific day assignments' do
|
65
|
+
let(:assignments_config) do
|
66
|
+
{
|
67
|
+
'default_day': [ assignment_1, assignment_2 ],
|
68
|
+
'2016-02-29': [],
|
69
|
+
'2016-03-01': [ assignment_2 ],
|
70
|
+
}.stringify_keys
|
71
|
+
end
|
72
|
+
let(:start_date) { Date.parse('2016-02-29') }
|
73
|
+
let(:end_date) { Date.parse('2016-03-01') }
|
74
|
+
|
75
|
+
it 'uses specific day assignments' do
|
76
|
+
time_entries = subject.generate(start_date, end_date)
|
77
|
+
|
78
|
+
expect(time_entries.count).to eq(1)
|
79
|
+
time_entry = time_entries.first
|
80
|
+
expect(time_entry.date).to eq('2016-03-01')
|
81
|
+
expect(time_entry.total_hours).to eq(3)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module Dovico
|
4
|
+
describe Dovico::TimeEntry do
|
5
|
+
subject do
|
6
|
+
Dovico::TimeEntry.parse(time_entry_api_hash)
|
7
|
+
end
|
8
|
+
let(:project_hash) do
|
9
|
+
{
|
10
|
+
"ID": "111"
|
11
|
+
}.stringify_keys
|
12
|
+
end
|
13
|
+
let(:task_hash) do
|
14
|
+
{
|
15
|
+
"ID": "222"
|
16
|
+
}.stringify_keys
|
17
|
+
end
|
18
|
+
let(:employee_hash) do
|
19
|
+
{
|
20
|
+
"ID": "333"
|
21
|
+
}.stringify_keys
|
22
|
+
end
|
23
|
+
let(:time_entry_api_hash) do
|
24
|
+
{
|
25
|
+
"ID": "456",
|
26
|
+
"StartTime": "0800",
|
27
|
+
"StopTime": "1100",
|
28
|
+
"Project": project_hash,
|
29
|
+
"Task": task_hash,
|
30
|
+
"Employee": employee_hash,
|
31
|
+
"Date": "2017-01-05",
|
32
|
+
"TotalHours": "3",
|
33
|
+
"Description": "Unit test",
|
34
|
+
}.stringify_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
describe ".parse" do
|
38
|
+
it "parses the API hash" do
|
39
|
+
time_entry = Dovico::TimeEntry.parse(time_entry_api_hash)
|
40
|
+
|
41
|
+
expect(time_entry).to be_an(Dovico::TimeEntry)
|
42
|
+
expect(time_entry.start_time).to eq('0800')
|
43
|
+
expect(time_entry.project_id).to eq('111')
|
44
|
+
expect(time_entry.employee_id).to eq('333')
|
45
|
+
expect(time_entry.total_hours).to eq('3')
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with a GUID ID' do
|
49
|
+
it 'removes the first character of the GUID' do
|
50
|
+
time_entry = Dovico::TimeEntry.parse(
|
51
|
+
time_entry_api_hash.merge(
|
52
|
+
{"ID": "T0bb4860f-2669-49ab-ae5e-dd8908366c8b"}.stringify_keys
|
53
|
+
)
|
54
|
+
)
|
55
|
+
|
56
|
+
expect(time_entry.id).to eq('0bb4860f-2669-49ab-ae5e-dd8908366c8b')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".get" do
|
62
|
+
let(:time_entry_list_api) do
|
63
|
+
{
|
64
|
+
"TimeEntries": [ time_entry_api_hash ]
|
65
|
+
}.stringify_keys
|
66
|
+
end
|
67
|
+
|
68
|
+
before do
|
69
|
+
allow(ApiClient).to receive(:get).and_return(time_entry_list_api)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'calls the API and return an TimeEntry object' do
|
73
|
+
time_entry = Dovico::TimeEntry.get('999')
|
74
|
+
|
75
|
+
expect(ApiClient).to have_received(:get).with("#{Dovico::TimeEntry::URL_PATH}/999")
|
76
|
+
expect(time_entry).to be_an(Dovico::TimeEntry)
|
77
|
+
expect(time_entry.id).to eq('456')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe ".batch_create!" do
|
82
|
+
let(:assignments) do
|
83
|
+
[ subject ]
|
84
|
+
end
|
85
|
+
|
86
|
+
before do
|
87
|
+
allow(ApiClient).to receive(:post)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'calls the API and post TimeEntries objects' do
|
91
|
+
Dovico::TimeEntry.batch_create!(assignments)
|
92
|
+
|
93
|
+
expect(ApiClient).to have_received(:post).with(
|
94
|
+
"#{Dovico::TimeEntry::URL_PATH}",
|
95
|
+
body: [subject.to_api].to_json
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe ".submit!" do
|
101
|
+
let(:start_date) { Date.parse("2017-01-02") }
|
102
|
+
let(:end_date) { Date.parse("2017-01-12") }
|
103
|
+
let(:employee_id) { "333" }
|
104
|
+
|
105
|
+
before do
|
106
|
+
allow(ApiClient).to receive(:post)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'calls the API and submit a time range' do
|
110
|
+
Dovico::TimeEntry.submit!(employee_id, start_date, end_date)
|
111
|
+
|
112
|
+
expect(ApiClient).to have_received(:post).with(
|
113
|
+
"#{Dovico::TimeEntry::URL_PATH}/Employee/#{employee_id}/Submit",
|
114
|
+
params: {
|
115
|
+
daterange: "2017-01-02 2017-01-12"
|
116
|
+
},
|
117
|
+
body: {}.to_json
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe ".create!" do
|
123
|
+
before do
|
124
|
+
allow(ApiClient).to receive(:post)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'calls the API and post TimeEntries objects' do
|
128
|
+
subject.create!
|
129
|
+
|
130
|
+
expect(ApiClient).to have_received(:post).with(
|
131
|
+
"#{Dovico::TimeEntry::URL_PATH}",
|
132
|
+
body: [subject.to_api].to_json
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe ".update!" do
|
138
|
+
before do
|
139
|
+
allow(ApiClient).to receive(:put)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'calls the API and post TimeEntries objects' do
|
143
|
+
subject.update!
|
144
|
+
|
145
|
+
expect(ApiClient).to have_received(:put).with(
|
146
|
+
"#{Dovico::TimeEntry::URL_PATH}",
|
147
|
+
body: [subject.to_api].to_json
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe ".to_api" do
|
153
|
+
it 'serializes the object' do
|
154
|
+
expect(subject.to_api).to eq(
|
155
|
+
"ID" => "456",
|
156
|
+
"EmployeeID" => "333",
|
157
|
+
"ProjectID" => "111",
|
158
|
+
"TaskID" => "222",
|
159
|
+
"Date" => "2017-01-05",
|
160
|
+
"StartTime" => "0800",
|
161
|
+
"StopTime" => "1100",
|
162
|
+
"TotalHours" => "3",
|
163
|
+
"Description" => "Unit test",
|
164
|
+
)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|