@live-change/pattern 0.2.0 → 0.2.1

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.
package/index.js CHANGED
@@ -5,5 +5,6 @@ module.exports = {
5
5
  ...require('./lib/graph.js'),
6
6
  ...require('./lib/combinations.js'),
7
7
  ...require('./lib/duration.js'),
8
- ...require('./lib/relations-store.js')
8
+ ...require('./lib/relations-store.js'),
9
+ ...require('./lib/match.js'),
9
10
  }
package/lib/graph.js CHANGED
@@ -5,13 +5,13 @@ function updateGraph(event, transitions, aggregation, addToNode, addToRelation)
5
5
  for(const transition of transitions) {
6
6
  if(transition.relation && mark) mark(transition.relation)
7
7
  const ids = aggregation.nodes(transition, event.keys)
8
+ console.log("TR", transition, 'KEYS', event.keys, 'IDS', ids)
8
9
  for(let id of ids) {
9
10
  addToNode(id, transition, event, !transition.relation)
10
11
  if (transition.relation != null) {
11
12
  for(const prev of transition.relation.prev) {
12
- //console.log("PREV", prev, transition.relation)
13
13
  const prevIds = aggregation.nodes(prev, transition.relation && transition.relation.keys)
14
- for (const prevId of prevIds) {
14
+ for(const prevId of prevIds) {
15
15
  addToRelation(id, prevId, transition, event, true)
16
16
  addToRelation(prevId, id, transition, event, false)
17
17
  }
@@ -69,10 +69,10 @@ const agg = {
69
69
  relationEq: (transition, otherId) => (rel) => rel.to == otherId,
70
70
  },
71
71
  nodeElementDepth: {
72
- nodes: ({ element, relation }, keys) => [element + ':' + (relation ? relation.depth : 0)],
72
+ nodes: ({ element, relation }, keys) => [element + ':' + (relation?.depth ?? 0)],
73
73
  mark: (event) =>
74
74
  (rel) => rel.depth = (rel.prev && rel.prev.length)
75
- ? rel.prev.reduce((a, b) => Math.max(a, b.relation ? b.relation.depth : 0), 0) + 1
75
+ ? rel.prev.reduce((a, b) => Math.max(a, b?.relation?.depth ?? 0), 0) + 1
76
76
  : 0
77
77
  },
78
78
  nodeElement: {
@@ -106,30 +106,32 @@ class SummaryGraphProcessor extends LiveProcessor {
106
106
  this.removeCanceledRelations(canceledRelations)
107
107
 
108
108
  await updateGraph(event, transitions, this.aggregation,
109
- (id, transition, event, start) => {
110
- let node = this.graph.get(id)
111
- if (!node) {
112
- node = {
113
- id,
114
- prev: [],
115
- next: [],
116
- start: false
117
- }
118
- this.graph.set(id, node)
109
+ (id, transition, event, start) => {
110
+ let node = this.graph.get(id)
111
+ if (!node) {
112
+ node = {
113
+ id,
114
+ prev: [],
115
+ next: [],
116
+ start: false
119
117
  }
120
- this.aggregation.addToNode(node, transition, event)
121
- if(start) node.start = true
122
- },
123
- (fromId, toId, transition, event, prev) => {
124
- let node = this.graph.get(fromId)
125
- const list = (prev ? node.prev : node.next)
126
- let rel = list.find(this.aggregation.relationEq(transition, toId))
127
- if(!rel) {
128
- rel = this.aggregation.relationFactory(transition, toId)
129
- list.push(rel)
130
- }
131
- this.aggregation.addToRelation(rel, transition, event)
118
+ this.graph.set(id, node)
119
+ }
120
+ this.aggregation.addToNode(node, transition, event)
121
+ if(start) node.start = true
122
+ },
123
+ (fromId, toId, transition, event, prev) => {
124
+ let node = this.graph.get(fromId)
125
+ if(!node) console.log("NODE", node, fromId)
126
+ if(!node) throw new Error('node '+fromId+' not found when adding relation')
127
+ const list = (prev ? node.prev : node.next)
128
+ let rel = list.find(this.aggregation.relationEq(transition, toId))
129
+ if(!rel) {
130
+ rel = this.aggregation.relationFactory(transition, toId)
131
+ list.push(rel)
132
132
  }
133
+ this.aggregation.addToRelation(rel, transition, event)
134
+ }
133
135
  )
134
136
  }
135
137
  }
package/lib/live.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { parseDuration } = require('./duration.js')
2
+ const { eventElementMatch } = require('./match.js')
2
3
 
3
4
  function relationDescriptors(model, source, now, relation, keys, prev, withCancel = true) {
4
5
  let descriptors = []
@@ -45,8 +46,9 @@ function findDescriptor(that, list) {
45
46
  }
46
47
 
47
48
  async function processEvent(event, model, getRelationsByEvent) {
48
- const { type, keys } = event
49
+ const { type, keys, properties } = event
49
50
  const nextRelations = await getRelationsByEvent(type, keys)
51
+ //console.log("EVENT", event, "NEXT RELATIONS", nextRelations)
50
52
 
51
53
  let newRelations = []
52
54
  let canceledRelations = []
@@ -58,7 +60,7 @@ async function processEvent(event, model, getRelationsByEvent) {
58
60
  if(findDescriptor(relationDescriptor, canceledRelations)) continue
59
61
  const relationModel = model.relations[relationDescriptor.relation]
60
62
  //console.log("NEXT RELATION MODEL", relationModel)
61
- const nextElements = relationModel.next.filter(id => model.elements[id].type == type)
63
+ const nextElements = relationModel.next.filter(id => eventElementMatch(event, model.elements[id]))
62
64
  //console.log("NEXT ELEMENTS", nextElements)
63
65
  if (nextElements.length > 0) {
64
66
  if(relationModel.cancel) {
@@ -83,6 +85,7 @@ async function processEvent(event, model, getRelationsByEvent) {
83
85
  if(newElements) {
84
86
  for (const element of newElements) {
85
87
  const elementModel = model.elements[element]
88
+ if(!eventElementMatch(event, elementModel)) continue
86
89
  //console.log("NEW ELEMENT", element, elementModel)
87
90
  let transition = { element, relation: null }
88
91
  transitions.push(transition)
@@ -161,9 +164,10 @@ class LiveProcessor {
161
164
 
162
165
  const { newRelations, canceledRelations, actions } = changes
163
166
 
164
- /* console.log("PROCESSING RESULTS:")
167
+ /*
168
+ console.log("PROCESSING RESULTS:")
165
169
  console.log("NR: ", newRelations)
166
- console.log("CR: ", canceledRelations)*/
170
+ console.log("CR: ", canceledRelations)//*/
167
171
 
168
172
  await this.applyChanges(changes, event)
169
173
  return actions
@@ -197,8 +201,10 @@ class LiveProcessor {
197
201
  }
198
202
 
199
203
  async applyChanges({ newRelations, canceledRelations }, event) {
200
- this.addNewRelations(newRelations)
201
- this.removeCanceledRelations(canceledRelations)
204
+ await Promise.all([
205
+ this.addNewRelations(newRelations),
206
+ this.removeCanceledRelations(canceledRelations)
207
+ ])
202
208
  }
203
209
  }
204
210
 
package/lib/match.js ADDED
@@ -0,0 +1,27 @@
1
+ function matchProperties(match, properties) {
2
+ for(const key in match) {
3
+ const value = match[key]
4
+ const property = properties[key]
5
+ if(typeof value == 'object') {
6
+ if(typeof property != 'object') return false
7
+ if(!matchProperties(value, property)) return false
8
+ } else {
9
+ if(value != property) return false
10
+ }
11
+ }
12
+ return true
13
+ }
14
+
15
+ function eventElementMatch(event, element) {
16
+ console.log("CHECK ELEMENT", element)
17
+ if(element.type != event.type) return false
18
+ if(element.match) {
19
+ console.log("MATCH PROPERTIES", event, element)
20
+ if(!event.properties) return false
21
+ if(!matchProperties(element.match, event.properties)) return false
22
+ }
23
+ console.log("ELEMENT FOUND", event, element)
24
+ return true
25
+ }
26
+
27
+ module.exports = { eventElementMatch, matchProperties }
@@ -18,7 +18,7 @@ function relationsStore() {
18
18
  let eventRelations = new Map() // By event and key and value
19
19
  let eventRelationsBySource = new Map()
20
20
 
21
- async function getRelations(type, keys) {
21
+ async function getRelations(type, keys, properties) {
22
22
  const keyIds = eventRelationKeys(type, keys)
23
23
  const found = keyIds.map(key => eventRelations.get(key) || []).reduce((a,b) => a.concat(b), [])
24
24
  if(found.length == 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/pattern",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Realtime usage pattern analysis for security, analytics and gamification purposes",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -10,11 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/live-change/pattern.git"
12
12
  },
13
- "author": {
14
- "email": "m8@em8.pl",
15
- "name": "Michał Łaszczewski",
16
- "url": "https://www.viamage.com/"
17
- },
13
+ "author": "Michał Łaszczewski <m8@em8.pl> (https://www.viamage.com/)",
18
14
  "license": "MIT",
19
15
  "bugs": {
20
16
  "url": "https://github.com/live-change/pattern/issues"
@@ -26,5 +22,10 @@
26
22
  "d3-path-arrows": "^0.4.0",
27
23
  "d3-sankey-circular": "^0.34.0",
28
24
  "tape": "^4.13.0"
29
- }
25
+ },
26
+ "directories": {
27
+ "lib": "lib",
28
+ "test": "tests"
29
+ },
30
+ "keywords": []
30
31
  }
@@ -1,5 +1,5 @@
1
1
  const test = require('tape')
2
- const rp = require('../index.js')
2
+ const lcp = require('../index.js')
3
3
 
4
4
  let model
5
5
 
@@ -7,7 +7,7 @@ test("compile fail2ban chain with expire", (t) => {
7
7
 
8
8
  t.plan(2)
9
9
 
10
- let { model: compiled, last } = rp.chain([
10
+ let { model: compiled, last } = lcp.chain([
11
11
  "failed-login",
12
12
  { eq: "ip", expire: "2m" },
13
13
  "failed-login"
@@ -78,8 +78,8 @@ test("compile fail2ban chain with expire", (t) => {
78
78
  t.test('live processor', (t) => {
79
79
  t.plan(4)
80
80
 
81
- rp.prepareModelForLive(model)
82
- const processor = new rp.LiveProcessor(model, rp.relationsStore())
81
+ lcp.prepareModelForLive(model)
82
+ const processor = new lcp.LiveProcessor(model, lcp.relationsStore())
83
83
  const ip = (Math.random()*1000).toFixed()
84
84
  let time = 0
85
85
 
@@ -89,6 +89,8 @@ test("compile fail2ban chain with expire", (t) => {
89
89
  console.log("EVT", processor.store.eventRelations)
90
90
  console.log("TO", processor.timeouts)
91
91
  console.log("ACTIONS", actions)
92
+ const relations = await processor.store.getRelations('failed-login', { ip })
93
+ console.log("RELATIONS", JSON.stringify(relations, null, " "))
92
94
  if(processor.store.eventRelations.get(`["failed-login",[["ip","${ip}"]]]`)) t.pass('processed')
93
95
  else t.fail('no reaction')
94
96
  })
@@ -0,0 +1,192 @@
1
+ const test = require('tape')
2
+ const lcp = require('../index.js')
3
+
4
+ let model
5
+
6
+ test("compile fail2ban chain with expire", (t) => {
7
+
8
+ t.plan(3)
9
+
10
+ let { model: compiled, last } = lcp.chain([
11
+ { type: "failed-authentication", match: { secretType: 'code' } },
12
+ "ip",
13
+ { type: "failed-authentication", match: { secretType: 'code' } }
14
+ ])
15
+
16
+ compiled.elements[last].actions = [ 'ban' ]
17
+
18
+ console.log(JSON.stringify(compiled, null, ' '))
19
+
20
+ model = compiled
21
+
22
+ t.deepEqual(model, {
23
+ "elements": {
24
+ "failed-authentication": {
25
+ "type": "failed-authentication",
26
+ "match": {
27
+ "secretType": "code"
28
+ },
29
+ "id": "failed-authentication",
30
+ "prev": [],
31
+ "next": [
32
+ "failed-authentication/ip/failed-authentication"
33
+ ]
34
+ },
35
+ "failed-authentication/ip/failed-authentication": {
36
+ "type": "failed-authentication",
37
+ "match": {
38
+ "secretType": "code"
39
+ },
40
+ "id": "failed-authentication/ip/failed-authentication",
41
+ "prev": [
42
+ "failed-authentication/ip/failed-authentication"
43
+ ],
44
+ "next": [],
45
+ "actions": [
46
+ "ban"
47
+ ]
48
+ }
49
+ },
50
+ "relations": {
51
+ "failed-authentication/ip/failed-authentication": {
52
+ "eq": [
53
+ {
54
+ "prev": "ip",
55
+ "next": "ip"
56
+ }
57
+ ],
58
+ "id": "failed-authentication/ip/failed-authentication",
59
+ "prev": [
60
+ "failed-authentication"
61
+ ],
62
+ "next": [
63
+ "failed-authentication/ip/failed-authentication"
64
+ ]
65
+ }
66
+ }
67
+ })
68
+
69
+ t.test('live processor with second event filtered', (t) => {
70
+ t.plan(3)
71
+
72
+ lcp.prepareModelForLive(model)
73
+ const processor = new lcp.LiveProcessor(model, lcp.relationsStore())
74
+ const ip = (Math.random() * 1000).toFixed()
75
+ let time = 0
76
+
77
+ t.test('push first event', async (t) => {
78
+ t.plan(1)
79
+ const actions = await processor.processEvent({
80
+ id: 1, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'code' }, time
81
+ }, 0)
82
+ console.log("EVT", processor.store.eventRelations)
83
+ console.log("TO", processor.timeouts)
84
+ console.log("ACTIONS", actions)
85
+ const relations = await processor.store.getRelations('failed-authentication', { ip })
86
+ console.log("RELATIONS", JSON.stringify(relations, null, " "))
87
+ if(processor.store.eventRelations.get(`["failed-authentication",[["ip","${ip}"]]]`)) {
88
+ t.pass('processed')
89
+ } else {
90
+ t.fail('no reaction')
91
+ }
92
+ })
93
+
94
+ t.test('push second event', async (t) => {
95
+ t.plan(2)
96
+ time += 1000
97
+ const actions = await processor.processEvent({
98
+ id: 2, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'link' }, time
99
+ }, 0)
100
+ console.log("EVT", processor.store.eventRelations)
101
+ console.log("TO", processor.timeouts)
102
+ const relations = await processor.store.getRelations('failed-authentication', { ip })
103
+ console.log("RELATIONS", JSON.stringify(relations, null, " "))
104
+ console.log("ACTIONS", actions)
105
+ if(actions.length > 0) {
106
+ t.fail('actions where it should be filtered')
107
+ } else {
108
+ t.pass('event filtered')
109
+ }
110
+ if(processor.store.eventRelations.get(`["failed-authentication",[["ip","${ip}"]]]`)) {
111
+ t.pass('previous event exist')
112
+ } else {
113
+ t.fail('previous event not exist')
114
+ }
115
+ })
116
+
117
+ t.test('push third event', async (t) => {
118
+ t.plan(1)
119
+ time += 1000
120
+ const actions = await processor.processEvent({
121
+ id: 3, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'code' }, time
122
+ }, 0)
123
+ console.log("ACTIONS", actions)
124
+ t.deepEqual(actions, ['ban'], 'actions match')
125
+ })
126
+
127
+ })
128
+
129
+ t.test('live processor with first event filtered', (t) => {
130
+ t.plan(3)
131
+
132
+ lcp.prepareModelForLive(model)
133
+ const processor = new lcp.LiveProcessor(model, lcp.relationsStore())
134
+ const ip = (Math.random() * 1000).toFixed()
135
+ let time = 0
136
+
137
+ t.test('push first event', async (t) => {
138
+ t.plan(1)
139
+ const actions = await processor.processEvent({
140
+ id: 1, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'link' }, time
141
+ }, 0)
142
+ console.log("EVT", processor.store.eventRelations)
143
+ console.log("TO", processor.timeouts)
144
+ console.log("ACTIONS", actions)
145
+ const relations = await processor.store.getRelations('failed-authentication', { ip })
146
+ console.log("RELATIONS", JSON.stringify(relations, null, " "))
147
+ if(!processor.store.eventRelations.get(`["failed-authentication",[["ip","${ip}"]]]`)) {
148
+ t.pass('filtered')
149
+ } else {
150
+ t.fail('processed')
151
+ }
152
+ })
153
+
154
+ t.test('push second event', async (t) => {
155
+ t.plan(2)
156
+ time += 1000
157
+ const actions = await processor.processEvent({
158
+ id: 1, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'code' }, time
159
+ }, 0)
160
+ console.log("EVT", processor.store.eventRelations)
161
+ console.log("TO", processor.timeouts)
162
+ console.log("ACTIONS", actions)
163
+ const relations = await processor.store.getRelations('failed-authentication', { ip })
164
+ console.log("RELATIONS", JSON.stringify(relations, null, " "))
165
+ if(processor.store.eventRelations.get(`["failed-authentication",[["ip","${ip}"]]]`)) {
166
+ t.pass('processed')
167
+ } else {
168
+ t.fail('no reaction')
169
+ }
170
+ console.log("ACTIONS", actions)
171
+ if(actions.length > 0) {
172
+ t.fail('actions where it should be filtered')
173
+ } else {
174
+ t.pass('no action')
175
+ }
176
+ })
177
+
178
+ t.test('push third event', async (t) => {
179
+ t.plan(1)
180
+ time += 1000
181
+ const actions = await processor.processEvent({
182
+ id: 3, type: 'failed-authentication', keys: { ip }, properties: { secretType: 'code' }, time
183
+ }, 0)
184
+ console.log("ACTIONS", actions)
185
+ t.deepEqual(actions, ['ban'], 'actions match')
186
+ })
187
+
188
+ })
189
+
190
+ })
191
+
192
+
File without changes
@@ -4,7 +4,7 @@ const svg = require('./svg.js')
4
4
 
5
5
  let model
6
6
 
7
- test("simple chain", (t) => {
7
+ test("viral loop", (t) => {
8
8
  t.plan(4)
9
9
 
10
10
  t.test('compile', (t) => {
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="NodePackageJsonFileManager">
4
- <packageJsonPaths />
5
- </component>
6
- </project>
@@ -1,19 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="masterDetails">
4
- <states>
5
- <state key="ProjectJDKs.UI">
6
- <settings>
7
- <last-edited>10</last-edited>
8
- <splitter-proportions>
9
- <option name="proportions">
10
- <list>
11
- <option value="0.2" />
12
- </list>
13
- </option>
14
- </splitter-proportions>
15
- </settings>
16
- </state>
17
- </states>
18
- </component>
19
- </project>
@@ -1,42 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <code_scheme name="Project" version="173">
3
- <option name="OTHER_INDENT_OPTIONS">
4
- <value>
5
- <option name="INDENT_SIZE" value="2" />
6
- <option name="TAB_SIZE" value="2" />
7
- </value>
8
- </option>
9
- <codeStyleSettings language="CSS">
10
- <indentOptions>
11
- <option name="INDENT_SIZE" value="2" />
12
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
13
- <option name="TAB_SIZE" value="2" />
14
- </indentOptions>
15
- </codeStyleSettings>
16
- <codeStyleSettings language="HTML">
17
- <indentOptions>
18
- <option name="INDENT_SIZE" value="2" />
19
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
20
- <option name="TAB_SIZE" value="2" />
21
- </indentOptions>
22
- </codeStyleSettings>
23
- <codeStyleSettings language="JSON">
24
- <indentOptions>
25
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
26
- <option name="TAB_SIZE" value="2" />
27
- </indentOptions>
28
- </codeStyleSettings>
29
- <codeStyleSettings language="JavaScript">
30
- <indentOptions>
31
- <option name="INDENT_SIZE" value="2" />
32
- <option name="TAB_SIZE" value="2" />
33
- </indentOptions>
34
- </codeStyleSettings>
35
- <codeStyleSettings language="SCSS">
36
- <indentOptions>
37
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
38
- <option name="TAB_SIZE" value="2" />
39
- </indentOptions>
40
- </codeStyleSettings>
41
- </code_scheme>
42
- </component>
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
4
- </state>
5
- </component>
package/.idea/misc.xml DELETED
@@ -1,9 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptSettings">
4
- <option name="languageLevel" value="ES6" />
5
- </component>
6
- <component name="ProjectRootManager">
7
- <output url="file://$PROJECT_DIR$/out" />
8
- </component>
9
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/reactive-pattern.iml" filepath="$PROJECT_DIR$/reactive-pattern.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,83 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ChangeListManager">
4
- <list default="true" id="4bcde310-3a88-4ac7-bcec-b705edb985bb" name="Default Changelist" comment="" />
5
- <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
6
- <option name="SHOW_DIALOG" value="false" />
7
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
8
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
9
- <option name="LAST_RESOLUTION" value="IGNORE" />
10
- </component>
11
- <component name="CodeStyleSettingsInfer">
12
- <option name="done" value="true" />
13
- </component>
14
- <component name="ComposerSettings">
15
- <execution>
16
- <executable />
17
- </execution>
18
- </component>
19
- <component name="FileTemplateManagerImpl">
20
- <option name="RECENT_TEMPLATES">
21
- <list>
22
- <option value="JavaScript File" />
23
- </list>
24
- </option>
25
- </component>
26
- <component name="JsFlowSettings">
27
- <service-enabled>true</service-enabled>
28
- <exe-path />
29
- <other-services-enabled>true</other-services-enabled>
30
- <auto-save>true</auto-save>
31
- </component>
32
- <component name="ProjectCodeStyleSettingsMigration">
33
- <option name="version" value="1" />
34
- </component>
35
- <component name="ProjectId" id="1UEIRU0fnQMKUQE3vydpnEMLQQ0" />
36
- <component name="PropertiesComponent">
37
- <property name="WebServerToolWindowFactoryState" value="false" />
38
- <property name="javascript.nodejs.core.library.configured.version" value="10.17.0" />
39
- <property name="last_opened_file_path" value="$PROJECT_DIR$" />
40
- <property name="nodejs_package_manager_path" value="npm" />
41
- <property name="settings.editor.selected.configurable" value="Settings.JavaScript" />
42
- </component>
43
- <component name="RecentsManager">
44
- <key name="MoveFile.RECENT_KEYS">
45
- <recent name="$PROJECT_DIR$/.." />
46
- </key>
47
- <key name="CopyFile.RECENT_KEYS">
48
- <recent name="$PROJECT_DIR$/tests" />
49
- </key>
50
- </component>
51
- <component name="RunDashboard">
52
- <option name="ruleStates">
53
- <list>
54
- <RuleState>
55
- <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
56
- </RuleState>
57
- <RuleState>
58
- <option name="name" value="StatusDashboardGroupingRule" />
59
- </RuleState>
60
- </list>
61
- </option>
62
- </component>
63
- <component name="SvnConfiguration">
64
- <configuration />
65
- </component>
66
- <component name="TaskManager">
67
- <task active="true" id="Default" summary="Default task">
68
- <changelist id="4bcde310-3a88-4ac7-bcec-b705edb985bb" name="Default Changelist" comment="" />
69
- <created>1574915319580</created>
70
- <option name="number" value="Default" />
71
- <option name="presentableId" value="Default" />
72
- <updated>1574915319580</updated>
73
- <workItem from="1574915325078" duration="15213000" />
74
- <workItem from="1575015431540" duration="5716000" />
75
- <workItem from="1575024741938" duration="3246000" />
76
- <workItem from="1578040657122" duration="69000" />
77
- </task>
78
- <servers />
79
- </component>
80
- <component name="TypeScriptGeneratedFilesManager">
81
- <option name="version" value="1" />
82
- </component>
83
- </project>
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager" inherit-compiler-output="true">
4
- <exclude-output />
5
- <content url="file://$USER_HOME$/IdeaProjects/reactive-pattern" />
6
- <orderEntry type="sourceFolder" forTests="false" />
7
- </component>
8
- </module>