mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-14 11:21:29 +00:00
test: implement unit tests for hooks and refactor some types (#493)
This commit is contained in:
326
package-lock.json
generated
326
package-lock.json
generated
@@ -1816,176 +1816,171 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/actions": {
|
"node_modules/@interactjs/actions": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/actions/-/actions-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/actions/-/actions-1.10.27.tgz",
|
||||||
"integrity": "sha512-wyB1ZqpaZy5gmz6VDqK9KWh98xKnFgL7VyLvxHODFi9V0IYX4HJAAOBlhtfze0D1R1f1cY+gqPDK+dLaHMlE+w==",
|
"integrity": "sha512-FCRg5KwB+stkPcAMx/Cn0fgGP6p4LyMX9S/Upcn/W+hpYme31bPi54PCqmOebzz6myTthN6zFf9jMyLOqtI/gg==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/auto-scroll": {
|
"node_modules/@interactjs/auto-scroll": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/auto-scroll/-/auto-scroll-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/auto-scroll/-/auto-scroll-1.10.27.tgz",
|
||||||
"integrity": "sha512-IQcW7N3xOaoL8RnAGOGMk0Y2gue7L4S3BT6Id4VBBu8so163DtLiZVW6jXu9rKVntzbluaAeqNZlfAVyu3kIWg==",
|
"integrity": "sha512-zPg5TnVsZv+9Hnt4qnbxLvBMf+rIWHkoJVoSETEbLNaj90C8hIyr0pVwukSUySSgDhCgQ7np0f3pg4INLq9beQ==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/auto-start": {
|
"node_modules/@interactjs/auto-start": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/auto-start/-/auto-start-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/auto-start/-/auto-start-1.10.27.tgz",
|
||||||
"integrity": "sha512-qYVxhAbYnwxjD/NLEegUoAST7WASJ4VmWNjsyWRx/js5Op+I4E2zteARIeZGgrutcGIXMCcQzhCMgE3PjOpbpw==",
|
"integrity": "sha512-ECLBO/nxmaF1knncJKIE5F7la3KKRgEkn0Cu2JTPOYj9xy/LpfYElo3wkRHsodgOqF651nR70GK2/IzPR2lO9A==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/core": {
|
"node_modules/@interactjs/core": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.27.tgz",
|
||||||
"integrity": "sha512-rL9w+83HDRuXub8Ezqs+97CYLl/ne7bLT/sAeduUWaxYhsW9iOqBoob9JnkkCZOaOsYizWI1EWy0+fNc5ibtLQ==",
|
"integrity": "sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/dev-tools": {
|
"node_modules/@interactjs/dev-tools": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/dev-tools/-/dev-tools-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/dev-tools/-/dev-tools-1.10.27.tgz",
|
||||||
"integrity": "sha512-Oi9nEw3FfSwkNmW+V0WwdHqvzEkVHc24mH1v5EjRn60sqgrGLK9nTQ+NSuqcnUY8GxC3TkyuxnsOodxiadIRmA==",
|
"integrity": "sha512-YolmBwRaKH1gWbvyLeV3m5QSwtD38lOZnCBA87PCAlcd9PQAC2gb03fEPeEyD336bE20oLB8f0WZt4Wre+afiw==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27",
|
||||||
|
"vue": "3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/modifiers": "1.10.17",
|
"@interactjs/modifiers": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/inertia": {
|
"node_modules/@interactjs/inertia": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/inertia/-/inertia-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/inertia/-/inertia-1.10.27.tgz",
|
||||||
"integrity": "sha512-41vbYUjZIDCKt2/yhmjPrEW5+0uoL/hldFsll9pkvnLhmm12Xk0VXOlmR2zXKAmsTK3fJlKMyBYUX92qHLkyVQ==",
|
"integrity": "sha512-S/SVj/M0D+wWWPVXHcXN/YUWOK51LFJsEA+CTgVnFhlSU04+1FUvNLwilCZcHgECu1RJxZNKDwZysDATg+r8jQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@interactjs/offset": "1.10.17"
|
"@interactjs/offset": "1.10.27"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/modifiers": "1.10.17",
|
"@interactjs/modifiers": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/interact": {
|
"node_modules/@interactjs/interact": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/interact/-/interact-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/interact/-/interact-1.10.27.tgz",
|
||||||
"integrity": "sha512-NyKsf8EFudvdahBjPz1Gt5QnynVwa/2LUfBc2/w8QOnOBiyzUm0HLloJSaB8a50QbQkSWN243/Lgpd8GTMQvuQ==",
|
"integrity": "sha512-XdH3A2UUzjEFGGJgFuJlhiz99tE8jB8xNh/DmnoMuL6uOQPxNA+sWRnzEVjG0+zY2P3/dbhEpi4Cn3FLPzydwA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/types": "1.10.17",
|
"@interactjs/utils": "1.10.27"
|
||||||
"@interactjs/utils": "1.10.17"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/interactjs": {
|
"node_modules/@interactjs/interactjs": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/interactjs/-/interactjs-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/interactjs/-/interactjs-1.10.27.tgz",
|
||||||
"integrity": "sha512-hHmiukARbZhiM12zNKx0yQlFVl4C+NMeYNAYh6Mf9U3ZziQ47C+JEW8Gr7Zr/MxfNZyPu5nLKCpVQjh/JvBO9g==",
|
"integrity": "sha512-UwhfUZMZVXUY72efPABuKSBz1sUY+r+49v8t6Ku9o5Jq76AKg9mwmdGszIlOn3ppnFDDjvtzK/8TL+Sbd0EQEA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@interactjs/actions": "1.10.17",
|
"@interactjs/actions": "1.10.27",
|
||||||
"@interactjs/auto-scroll": "1.10.17",
|
"@interactjs/auto-scroll": "1.10.27",
|
||||||
"@interactjs/auto-start": "1.10.17",
|
"@interactjs/auto-start": "1.10.27",
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/dev-tools": "1.10.17",
|
"@interactjs/dev-tools": "1.10.27",
|
||||||
"@interactjs/inertia": "1.10.17",
|
"@interactjs/inertia": "1.10.27",
|
||||||
"@interactjs/interact": "1.10.17",
|
"@interactjs/interact": "1.10.27",
|
||||||
"@interactjs/modifiers": "1.10.17",
|
"@interactjs/modifiers": "1.10.27",
|
||||||
"@interactjs/offset": "1.10.17",
|
"@interactjs/offset": "1.10.27",
|
||||||
"@interactjs/pointer-events": "1.10.17",
|
"@interactjs/pointer-events": "1.10.27",
|
||||||
"@interactjs/reflow": "1.10.17",
|
"@interactjs/reflow": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/modifiers": {
|
"node_modules/@interactjs/modifiers": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.27.tgz",
|
||||||
"integrity": "sha512-Dxw8kv9VBIxnhNvQncR6CKAGMzKXczLvuAUIdSPFYtyerX/XiDulJUqhR+jVKNp/WjF1DvdBxWo0kGGLbM84LQ==",
|
"integrity": "sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@interactjs/snappers": "1.10.17"
|
"@interactjs/snappers": "1.10.27"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/offset": {
|
"node_modules/@interactjs/offset": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/offset/-/offset-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/offset/-/offset-1.10.27.tgz",
|
||||||
"integrity": "sha512-wWBnIQWgLrmJNTBbd/FdxHxAJjiXl/5ND8Jbw2DuP9gIGDxhFSdEt62Fgqimn9ICb8v8ycvSLObEmcvJF/8hQQ==",
|
"integrity": "sha512-AezsLiuK+Qv4jXdYuRa65HJ2pMFMZPlqiAep6ZRLwhP9HE7O75c0EAm+gfx+dpPrHNHs6J9LaiKSZl+B+A2qAw==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/pointer-events": {
|
"node_modules/@interactjs/pointer-events": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/pointer-events/-/pointer-events-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/pointer-events/-/pointer-events-1.10.27.tgz",
|
||||||
"integrity": "sha512-VsfluouEKb8QRGyH6jQATCW+QdAd/3dkENS7rj2m+EcVUhz2Ob5mpMRopjALi4pwltMowqTfuJ4LtwMSX2G29A==",
|
"integrity": "sha512-Yo5SS6PhWfC93gHNxnwwW0wvebo5hSYJKGaSnAHO4f9Lh25yibecMnmPBmiEfWVcdMboK/kXrme43mHQaRegVg==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/reflow": {
|
"node_modules/@interactjs/reflow": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/reflow/-/reflow-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/reflow/-/reflow-1.10.27.tgz",
|
||||||
"integrity": "sha512-ncpWP5k93FRQptEhjzPZsbuRRajd4rkW17lDavCrEjrDi/LHnYekWGqZTaFzfJ80n1x8xUm9ujDjxCTylNqEIA==",
|
"integrity": "sha512-Msm0QdYFr40oSsPFxyCR3dHN/pQx34k7QSkdN1uIsUn/drrm+YSFvrvVOu99DFOwr7gTThr5vNe06Sz4vubTSA==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/snappers": {
|
"node_modules/@interactjs/snappers": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/snappers/-/snappers-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/snappers/-/snappers-1.10.27.tgz",
|
||||||
"integrity": "sha512-m753DGsNOts797e3zDT6wqELoc+BlpIC1w+TyMyISRxU6n1RlS8Q6LHBGgwAgV79LHLaahv/a5haFF9H1VG0FQ==",
|
"integrity": "sha512-HZLZ0XSi6HI08OmTv/HKG6AltQoaKAALLQ+KDW92utj3XSgw7oren0KsWUKPhaPg3Av7R1jFQd08s+uafqIlLw==",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@interactjs/types": {
|
|
||||||
"version": "1.10.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.17.tgz",
|
|
||||||
"integrity": "sha512-X2JpoM7xUw0p9Me0tMaI0HNfcF/Hd07ZZlzpnpEMpGerUZOLoyeThrV9P+CrBHxZrluWJrigJbcdqXliFd0YMA=="
|
|
||||||
},
|
|
||||||
"node_modules/@interactjs/utils": {
|
"node_modules/@interactjs/utils": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.27.tgz",
|
||||||
"integrity": "sha512-sZAW08CkqgvqRjUIaLRjScjObcCzN9D75yekLA21EClYAZIhi4A+GEt2z/WqOCOksTaEPLYmQyhkpXcboc0LhQ=="
|
"integrity": "sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw=="
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/core-base": {
|
"node_modules/@intlify/core-base": {
|
||||||
"version": "9.14.5",
|
"version": "9.14.5",
|
||||||
@@ -15560,131 +15555,126 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@interactjs/actions": {
|
"@interactjs/actions": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/actions/-/actions-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/actions/-/actions-1.10.27.tgz",
|
||||||
"integrity": "sha512-wyB1ZqpaZy5gmz6VDqK9KWh98xKnFgL7VyLvxHODFi9V0IYX4HJAAOBlhtfze0D1R1f1cY+gqPDK+dLaHMlE+w==",
|
"integrity": "sha512-FCRg5KwB+stkPcAMx/Cn0fgGP6p4LyMX9S/Upcn/W+hpYme31bPi54PCqmOebzz6myTthN6zFf9jMyLOqtI/gg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/auto-scroll": {
|
"@interactjs/auto-scroll": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/auto-scroll/-/auto-scroll-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/auto-scroll/-/auto-scroll-1.10.27.tgz",
|
||||||
"integrity": "sha512-IQcW7N3xOaoL8RnAGOGMk0Y2gue7L4S3BT6Id4VBBu8so163DtLiZVW6jXu9rKVntzbluaAeqNZlfAVyu3kIWg==",
|
"integrity": "sha512-zPg5TnVsZv+9Hnt4qnbxLvBMf+rIWHkoJVoSETEbLNaj90C8hIyr0pVwukSUySSgDhCgQ7np0f3pg4INLq9beQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/auto-start": {
|
"@interactjs/auto-start": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/auto-start/-/auto-start-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/auto-start/-/auto-start-1.10.27.tgz",
|
||||||
"integrity": "sha512-qYVxhAbYnwxjD/NLEegUoAST7WASJ4VmWNjsyWRx/js5Op+I4E2zteARIeZGgrutcGIXMCcQzhCMgE3PjOpbpw==",
|
"integrity": "sha512-ECLBO/nxmaF1knncJKIE5F7la3KKRgEkn0Cu2JTPOYj9xy/LpfYElo3wkRHsodgOqF651nR70GK2/IzPR2lO9A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/core": {
|
"@interactjs/core": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.27.tgz",
|
||||||
"integrity": "sha512-rL9w+83HDRuXub8Ezqs+97CYLl/ne7bLT/sAeduUWaxYhsW9iOqBoob9JnkkCZOaOsYizWI1EWy0+fNc5ibtLQ==",
|
"integrity": "sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@interactjs/dev-tools": {
|
"@interactjs/dev-tools": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/dev-tools/-/dev-tools-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/dev-tools/-/dev-tools-1.10.27.tgz",
|
||||||
"integrity": "sha512-Oi9nEw3FfSwkNmW+V0WwdHqvzEkVHc24mH1v5EjRn60sqgrGLK9nTQ+NSuqcnUY8GxC3TkyuxnsOodxiadIRmA==",
|
"integrity": "sha512-YolmBwRaKH1gWbvyLeV3m5QSwtD38lOZnCBA87PCAlcd9PQAC2gb03fEPeEyD336bE20oLB8f0WZt4Wre+afiw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27",
|
||||||
|
"vue": "3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/inertia": {
|
"@interactjs/inertia": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/inertia/-/inertia-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/inertia/-/inertia-1.10.27.tgz",
|
||||||
"integrity": "sha512-41vbYUjZIDCKt2/yhmjPrEW5+0uoL/hldFsll9pkvnLhmm12Xk0VXOlmR2zXKAmsTK3fJlKMyBYUX92qHLkyVQ==",
|
"integrity": "sha512-S/SVj/M0D+wWWPVXHcXN/YUWOK51LFJsEA+CTgVnFhlSU04+1FUvNLwilCZcHgECu1RJxZNKDwZysDATg+r8jQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17",
|
"@interactjs/interact": "1.10.27",
|
||||||
"@interactjs/offset": "1.10.17"
|
"@interactjs/offset": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/interact": {
|
"@interactjs/interact": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/interact/-/interact-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/interact/-/interact-1.10.27.tgz",
|
||||||
"integrity": "sha512-NyKsf8EFudvdahBjPz1Gt5QnynVwa/2LUfBc2/w8QOnOBiyzUm0HLloJSaB8a50QbQkSWN243/Lgpd8GTMQvuQ==",
|
"integrity": "sha512-XdH3A2UUzjEFGGJgFuJlhiz99tE8jB8xNh/DmnoMuL6uOQPxNA+sWRnzEVjG0+zY2P3/dbhEpi4Cn3FLPzydwA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/types": "1.10.17",
|
"@interactjs/utils": "1.10.27"
|
||||||
"@interactjs/utils": "1.10.17"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/interactjs": {
|
"@interactjs/interactjs": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/interactjs/-/interactjs-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/interactjs/-/interactjs-1.10.27.tgz",
|
||||||
"integrity": "sha512-hHmiukARbZhiM12zNKx0yQlFVl4C+NMeYNAYh6Mf9U3ZziQ47C+JEW8Gr7Zr/MxfNZyPu5nLKCpVQjh/JvBO9g==",
|
"integrity": "sha512-UwhfUZMZVXUY72efPABuKSBz1sUY+r+49v8t6Ku9o5Jq76AKg9mwmdGszIlOn3ppnFDDjvtzK/8TL+Sbd0EQEA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/actions": "1.10.17",
|
"@interactjs/actions": "1.10.27",
|
||||||
"@interactjs/auto-scroll": "1.10.17",
|
"@interactjs/auto-scroll": "1.10.27",
|
||||||
"@interactjs/auto-start": "1.10.17",
|
"@interactjs/auto-start": "1.10.27",
|
||||||
"@interactjs/core": "1.10.17",
|
"@interactjs/core": "1.10.27",
|
||||||
"@interactjs/dev-tools": "1.10.17",
|
"@interactjs/dev-tools": "1.10.27",
|
||||||
"@interactjs/inertia": "1.10.17",
|
"@interactjs/inertia": "1.10.27",
|
||||||
"@interactjs/interact": "1.10.17",
|
"@interactjs/interact": "1.10.27",
|
||||||
"@interactjs/modifiers": "1.10.17",
|
"@interactjs/modifiers": "1.10.27",
|
||||||
"@interactjs/offset": "1.10.17",
|
"@interactjs/offset": "1.10.27",
|
||||||
"@interactjs/pointer-events": "1.10.17",
|
"@interactjs/pointer-events": "1.10.27",
|
||||||
"@interactjs/reflow": "1.10.17",
|
"@interactjs/reflow": "1.10.27",
|
||||||
"@interactjs/utils": "1.10.17"
|
"@interactjs/utils": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/modifiers": {
|
"@interactjs/modifiers": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.27.tgz",
|
||||||
"integrity": "sha512-Dxw8kv9VBIxnhNvQncR6CKAGMzKXczLvuAUIdSPFYtyerX/XiDulJUqhR+jVKNp/WjF1DvdBxWo0kGGLbM84LQ==",
|
"integrity": "sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17",
|
"@interactjs/interact": "1.10.27",
|
||||||
"@interactjs/snappers": "1.10.17"
|
"@interactjs/snappers": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/offset": {
|
"@interactjs/offset": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/offset/-/offset-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/offset/-/offset-1.10.27.tgz",
|
||||||
"integrity": "sha512-wWBnIQWgLrmJNTBbd/FdxHxAJjiXl/5ND8Jbw2DuP9gIGDxhFSdEt62Fgqimn9ICb8v8ycvSLObEmcvJF/8hQQ==",
|
"integrity": "sha512-AezsLiuK+Qv4jXdYuRa65HJ2pMFMZPlqiAep6ZRLwhP9HE7O75c0EAm+gfx+dpPrHNHs6J9LaiKSZl+B+A2qAw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/pointer-events": {
|
"@interactjs/pointer-events": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/pointer-events/-/pointer-events-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/pointer-events/-/pointer-events-1.10.27.tgz",
|
||||||
"integrity": "sha512-VsfluouEKb8QRGyH6jQATCW+QdAd/3dkENS7rj2m+EcVUhz2Ob5mpMRopjALi4pwltMowqTfuJ4LtwMSX2G29A==",
|
"integrity": "sha512-Yo5SS6PhWfC93gHNxnwwW0wvebo5hSYJKGaSnAHO4f9Lh25yibecMnmPBmiEfWVcdMboK/kXrme43mHQaRegVg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/reflow": {
|
"@interactjs/reflow": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/reflow/-/reflow-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/reflow/-/reflow-1.10.27.tgz",
|
||||||
"integrity": "sha512-ncpWP5k93FRQptEhjzPZsbuRRajd4rkW17lDavCrEjrDi/LHnYekWGqZTaFzfJ80n1x8xUm9ujDjxCTylNqEIA==",
|
"integrity": "sha512-Msm0QdYFr40oSsPFxyCR3dHN/pQx34k7QSkdN1uIsUn/drrm+YSFvrvVOu99DFOwr7gTThr5vNe06Sz4vubTSA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/snappers": {
|
"@interactjs/snappers": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/snappers/-/snappers-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/snappers/-/snappers-1.10.27.tgz",
|
||||||
"integrity": "sha512-m753DGsNOts797e3zDT6wqELoc+BlpIC1w+TyMyISRxU6n1RlS8Q6LHBGgwAgV79LHLaahv/a5haFF9H1VG0FQ==",
|
"integrity": "sha512-HZLZ0XSi6HI08OmTv/HKG6AltQoaKAALLQ+KDW92utj3XSgw7oren0KsWUKPhaPg3Av7R1jFQd08s+uafqIlLw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@interactjs/interact": "1.10.17"
|
"@interactjs/interact": "1.10.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@interactjs/types": {
|
|
||||||
"version": "1.10.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.17.tgz",
|
|
||||||
"integrity": "sha512-X2JpoM7xUw0p9Me0tMaI0HNfcF/Hd07ZZlzpnpEMpGerUZOLoyeThrV9P+CrBHxZrluWJrigJbcdqXliFd0YMA=="
|
|
||||||
},
|
|
||||||
"@interactjs/utils": {
|
"@interactjs/utils": {
|
||||||
"version": "1.10.17",
|
"version": "1.10.27",
|
||||||
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.17.tgz",
|
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.27.tgz",
|
||||||
"integrity": "sha512-sZAW08CkqgvqRjUIaLRjScjObcCzN9D75yekLA21EClYAZIhi4A+GEt2z/WqOCOksTaEPLYmQyhkpXcboc0LhQ=="
|
"integrity": "sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw=="
|
||||||
},
|
},
|
||||||
"@intlify/core-base": {
|
"@intlify/core-base": {
|
||||||
"version": "9.14.5",
|
"version": "9.14.5",
|
||||||
|
541
src/hooks/__tests__/useAssociateProcessor.spec.ts
Normal file
541
src/hooks/__tests__/useAssociateProcessor.spec.ts
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import useAssociateProcessor from "../useAssociateProcessor";
|
||||||
|
import type { EventParams } from "@/types/app";
|
||||||
|
import type { AssociateProcessorProps, FilterOption } from "@/types/dashboard";
|
||||||
|
|
||||||
|
// Mock the store
|
||||||
|
let mockAppStore: any;
|
||||||
|
vi.mock("@/store/modules/app", () => ({
|
||||||
|
useAppStoreWithOut: () => mockAppStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock utility functions
|
||||||
|
vi.mock("@/utils/dateFormat", () => ({
|
||||||
|
default: vi.fn((date: Date, step: string, monthDayDiff?: boolean) => {
|
||||||
|
if (step === "HOUR" && monthDayDiff) {
|
||||||
|
return "2023-01-01 12";
|
||||||
|
}
|
||||||
|
return "2023-01-01 12:00:00";
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/utils/localtime", () => ({
|
||||||
|
default: vi.fn((utc: boolean, date: Date) => new Date(date)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock structuredClone
|
||||||
|
const structuredCloneMock = vi.fn((obj: any) => JSON.parse(JSON.stringify(obj)));
|
||||||
|
Object.defineProperty(window, "structuredClone", {
|
||||||
|
value: structuredCloneMock,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to create mock legend options
|
||||||
|
const createMockLegendOptions = () => ({
|
||||||
|
show: false,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 0,
|
||||||
|
asSelector: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useAssociateProcessor", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockAppStore = {
|
||||||
|
utc: false,
|
||||||
|
intervalUnix: [1640995200000, 1640998800000, 1641002400000], // Sample timestamps
|
||||||
|
durationRow: { step: "HOUR" },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("eventAssociate", () => {
|
||||||
|
it("returns undefined when no filters provided", () => {
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
expect(result).toEqual({ series: [], type: "line", legend: createMockLegendOptions() });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns option when no duration in filters", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
data: [[1, 2]] as (number | string)[][],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
expect(result).toBe(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when no series data", () => {
|
||||||
|
const option: FilterOption = { series: [], type: "line", legend: createMockLegendOptions() };
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: {
|
||||||
|
dataIndex: 0,
|
||||||
|
sourceId: "test",
|
||||||
|
duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
|
||||||
|
},
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when endTime not in series data", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
data: [
|
||||||
|
[1000, 1],
|
||||||
|
[1500, 2],
|
||||||
|
] as (number | string)[][],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: {
|
||||||
|
dataIndex: 0,
|
||||||
|
sourceId: "test",
|
||||||
|
duration: { startTime: "1000", endTime: "3000", step: "HOUR" },
|
||||||
|
},
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds markArea when endTime exists in series data", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
data: [
|
||||||
|
["1000", 1],
|
||||||
|
["2000", 2],
|
||||||
|
["3000", 3],
|
||||||
|
] as (number | string)[][],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: {
|
||||||
|
dataIndex: 0,
|
||||||
|
sourceId: "test",
|
||||||
|
duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
|
||||||
|
},
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result?.series[0].markArea).toEqual({
|
||||||
|
silent: true,
|
||||||
|
itemStyle: { opacity: 0.3 },
|
||||||
|
data: [[{ xAxis: "1000" }, { xAxis: "2000" }]],
|
||||||
|
});
|
||||||
|
expect(structuredCloneMock).toHaveBeenCalledWith(option.series);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves other series properties when adding markArea", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Series1",
|
||||||
|
data: [
|
||||||
|
["1000", 1],
|
||||||
|
["2000", 2],
|
||||||
|
] as (number | string)[][],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Series2",
|
||||||
|
data: [
|
||||||
|
["1000", 3],
|
||||||
|
["2000", 4],
|
||||||
|
] as (number | string)[][],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: {
|
||||||
|
dataIndex: 0,
|
||||||
|
sourceId: "test",
|
||||||
|
duration: { startTime: "1000", endTime: "2000", step: "HOUR" },
|
||||||
|
},
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { eventAssociate } = useAssociateProcessor(mockProps);
|
||||||
|
const result = eventAssociate();
|
||||||
|
|
||||||
|
expect(result?.series).toHaveLength(2);
|
||||||
|
expect(result?.series[0].name).toBe("Series1");
|
||||||
|
expect(result?.series[0].markArea).toBeDefined();
|
||||||
|
expect(result?.series[1].name).toBe("Series2");
|
||||||
|
expect(result?.series[1].markArea).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("traceFilters", () => {
|
||||||
|
it("returns undefined when no currentParams provided", () => {
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const result = traceFilters(null);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns object with undefined duration when no start time in intervalUnix", () => {
|
||||||
|
mockAppStore.intervalUnix = [];
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result?.duration).toBeUndefined();
|
||||||
|
expect(result?.metricValue).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns trace filters with duration when start time exists", () => {
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result?.duration).toEqual({
|
||||||
|
startTime: "2023-01-01 12",
|
||||||
|
endTime: "2023-01-01 12",
|
||||||
|
step: "HOUR",
|
||||||
|
});
|
||||||
|
expect(result?.queryOrder).toBe("");
|
||||||
|
expect(result?.status).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes relatedTrace properties when provided", () => {
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "SUCCESS",
|
||||||
|
queryOrder: "BY_START_TIME",
|
||||||
|
latency: true,
|
||||||
|
enableRelate: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
|
||||||
|
expect(result?.status).toBe("SUCCESS");
|
||||||
|
expect(result?.queryOrder).toBe("BY_START_TIME");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates latency list when latency is enabled", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Service1",
|
||||||
|
data: [[1000, 100] as (number | string)[], [2000, 200] as (number | string)[]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Service2",
|
||||||
|
data: [[1000, 150] as (number | string)[], [2000, 250] as (number | string)[]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: true,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
|
||||||
|
expect(result?.latency).toHaveLength(2);
|
||||||
|
expect(result?.latency[0]).toEqual({
|
||||||
|
label: "Service1--Service2",
|
||||||
|
value: "0",
|
||||||
|
data: [100, 150],
|
||||||
|
});
|
||||||
|
expect(result?.latency[1]).toEqual({
|
||||||
|
label: "Service2--Infinity",
|
||||||
|
value: "1",
|
||||||
|
data: [150, Infinity],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates metricValue for all series", () => {
|
||||||
|
const option: FilterOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Service1",
|
||||||
|
data: [[1000, 100] as (number | string)[], [2000, 200] as (number | string)[]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Service2",
|
||||||
|
data: [[1000, 150] as (number | string)[], [2000, 250] as (number | string)[]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
legend: createMockLegendOptions(),
|
||||||
|
};
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option,
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
|
||||||
|
expect(result?.metricValue).toHaveLength(2);
|
||||||
|
expect(result?.metricValue[0]).toEqual({
|
||||||
|
label: "Service1",
|
||||||
|
value: "0",
|
||||||
|
data: 100,
|
||||||
|
date: 1000,
|
||||||
|
});
|
||||||
|
expect(result?.metricValue[1]).toEqual({
|
||||||
|
label: "Service2",
|
||||||
|
value: "1",
|
||||||
|
data: 150,
|
||||||
|
date: 1000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty series gracefully", () => {
|
||||||
|
const mockProps: AssociateProcessorProps = {
|
||||||
|
filters: { dataIndex: 0, sourceId: "test" },
|
||||||
|
option: { series: [], type: "line", legend: createMockLegendOptions() },
|
||||||
|
relatedTrace: {
|
||||||
|
duration: { start: "0", end: "0", step: "HOUR" },
|
||||||
|
refIdType: "",
|
||||||
|
status: "",
|
||||||
|
queryOrder: "",
|
||||||
|
latency: false,
|
||||||
|
enableRelate: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { traceFilters } = useAssociateProcessor(mockProps);
|
||||||
|
const currentParams: EventParams = {
|
||||||
|
componentType: "chart",
|
||||||
|
seriesType: "line",
|
||||||
|
seriesIndex: 0,
|
||||||
|
seriesName: "test",
|
||||||
|
name: "test",
|
||||||
|
data: [1000, 1],
|
||||||
|
dataType: "number",
|
||||||
|
value: 1,
|
||||||
|
color: "#000",
|
||||||
|
event: {},
|
||||||
|
dataIndex: 0,
|
||||||
|
};
|
||||||
|
const result = traceFilters(currentParams);
|
||||||
|
|
||||||
|
expect(result?.metricValue).toEqual([]);
|
||||||
|
expect(result?.latency).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
178
src/hooks/__tests__/useBreakpoint.spec.ts
Normal file
178
src/hooks/__tests__/useBreakpoint.spec.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { createBreakpointListen, useBreakpoint } from "../useBreakpoint";
|
||||||
|
import { sizeEnum, screenMap } from "../data";
|
||||||
|
|
||||||
|
function setBodyClientWidth(width: number) {
|
||||||
|
Object.defineProperty(document.body, "clientWidth", {
|
||||||
|
value: width,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useBreakpoint", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.clearAllTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes with current width and calls callback once", () => {
|
||||||
|
setBodyClientWidth(400); // < XS(480)
|
||||||
|
|
||||||
|
const callback = vi.fn();
|
||||||
|
const { screenRef, widthRef, realWidthRef } = createBreakpointListen(callback);
|
||||||
|
|
||||||
|
// Initial values computed synchronously via getWindowWidth + resizeFn
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.XS);
|
||||||
|
expect(widthRef.value).toBe(screenMap.get(sizeEnum.XS));
|
||||||
|
expect(realWidthRef.value).toBe(400);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
const args = callback.mock.calls[0][0];
|
||||||
|
expect(args.screen.value).toBe(sizeEnum.XS);
|
||||||
|
expect(args.width.value).toBe(screenMap.get(sizeEnum.XS));
|
||||||
|
expect(args.realWidth.value).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates refs on resize (debounced)", () => {
|
||||||
|
setBodyClientWidth(500); // SM bucket
|
||||||
|
const callback = vi.fn();
|
||||||
|
const { screenRef, widthRef, realWidthRef } = createBreakpointListen(callback);
|
||||||
|
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.SM);
|
||||||
|
expect(widthRef.value).toBe(screenMap.get(sizeEnum.SM));
|
||||||
|
expect(realWidthRef.value).toBe(500);
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Change to 800 -> LG bucket
|
||||||
|
setBodyClientWidth(800);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
|
||||||
|
// Debounced by default (wait=80), so not yet updated
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.SM);
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// After debounce window
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.LG);
|
||||||
|
expect(widthRef.value).toBe(screenMap.get(sizeEnum.LG));
|
||||||
|
expect(realWidthRef.value).toBe(800);
|
||||||
|
expect(callback).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps widths across all breakpoints correctly", () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
|
||||||
|
// XS: < 480
|
||||||
|
setBodyClientWidth(479);
|
||||||
|
const a = createBreakpointListen(callback);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.XS);
|
||||||
|
expect(a.widthRef.value).toBe(screenMap.get(sizeEnum.XS));
|
||||||
|
expect(a.realWidthRef.value).toBe(479);
|
||||||
|
|
||||||
|
// SM: [480, 576)
|
||||||
|
setBodyClientWidth(500);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.SM);
|
||||||
|
|
||||||
|
// MD: [576, 768)
|
||||||
|
setBodyClientWidth(600);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.MD);
|
||||||
|
|
||||||
|
// LG: [768, 992)
|
||||||
|
setBodyClientWidth(800);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.LG);
|
||||||
|
|
||||||
|
// XL: [992, 1200)
|
||||||
|
setBodyClientWidth(1100);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.XL);
|
||||||
|
|
||||||
|
// XXL: >= 1200
|
||||||
|
setBodyClientWidth(2000);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(a.screenRef.value).toBe(sizeEnum.XXL);
|
||||||
|
expect(a.widthRef.value).toBe(screenMap.get(sizeEnum.XXL));
|
||||||
|
expect(a.realWidthRef.value).toBe(2000);
|
||||||
|
|
||||||
|
// Callback should have been called on init + each debounced resize
|
||||||
|
// init once + 5 resizes => 6 total
|
||||||
|
expect(callback).toHaveBeenCalledTimes(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("useBreakpoint exposes the same global refs", () => {
|
||||||
|
setBodyClientWidth(700); // MD bucket
|
||||||
|
createBreakpointListen();
|
||||||
|
|
||||||
|
const { screenRef, widthRef, realWidthRef } = useBreakpoint();
|
||||||
|
expect(screenRef).toBeDefined();
|
||||||
|
expect(widthRef).toBeDefined();
|
||||||
|
expect(realWidthRef).toBeDefined();
|
||||||
|
|
||||||
|
expect(screenRef).not.toBeNull();
|
||||||
|
expect(widthRef.value).toBe(screenMap.get(sizeEnum.MD));
|
||||||
|
expect(realWidthRef.value).toBe(700);
|
||||||
|
|
||||||
|
// Change to XXL and verify through useBreakpoint refs
|
||||||
|
setBodyClientWidth(1600);
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.XXL);
|
||||||
|
expect(widthRef.value).toBe(screenMap.get(sizeEnum.XXL));
|
||||||
|
expect(realWidthRef.value).toBe(1600);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("debounces multiple rapid resize events into a single update", () => {
|
||||||
|
setBodyClientWidth(750); // MD
|
||||||
|
const cb = vi.fn();
|
||||||
|
const { screenRef } = createBreakpointListen(cb);
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.MD);
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Rapid events with different widths; only final one should be applied after debounce
|
||||||
|
setBodyClientWidth(770); // still LG range? 770 >= 768 -> LG bucket
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
setBodyClientWidth(1000); // XL bucket boundary (< 1200)
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
setBodyClientWidth(1300); // XXL
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
|
||||||
|
// Before debounce timeout, nothing changes
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.MD);
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(80);
|
||||||
|
// Only the last width (1300) should be reflected
|
||||||
|
expect(screenRef.value).toBe(sizeEnum.XXL);
|
||||||
|
expect(cb).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
210
src/hooks/__tests__/useDashboardsSession.spec.ts
Normal file
210
src/hooks/__tests__/useDashboardsSession.spec.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { ConfigFieldTypes } from "@/views/dashboard/data";
|
||||||
|
import getDashboard from "../useDashboardsSession";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
// Mock ElMessage from element-plus
|
||||||
|
vi.mock("element-plus", () => ({
|
||||||
|
ElMessage: { info: vi.fn(), error: vi.fn(), success: vi.fn() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock dashboard store
|
||||||
|
let mockDashboardStore: any;
|
||||||
|
vi.mock("@/store/modules/dashboard", () => ({
|
||||||
|
useDashboardStore: () => mockDashboardStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
function setupContainers() {
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
const main = document.createElement("div");
|
||||||
|
main.className = "ds-main";
|
||||||
|
// allow scrollTop to be writable in jsdom
|
||||||
|
Object.defineProperty(main, "scrollTop", { value: 0, writable: true });
|
||||||
|
|
||||||
|
const tab = document.createElement("div");
|
||||||
|
tab.className = "tab-layout";
|
||||||
|
Object.defineProperty(tab, "scrollTop", { value: 0, writable: true });
|
||||||
|
|
||||||
|
document.body.appendChild(main);
|
||||||
|
document.body.appendChild(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useDashboardsSession", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
sessionStorage.clear();
|
||||||
|
setupContainers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sessionStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects dashboard by NAME using param and flattens widgets (including Tab children)", () => {
|
||||||
|
const dashboards = [
|
||||||
|
{ name: "A", layer: "L1", entity: "Service", isDefault: false },
|
||||||
|
{ name: "B", layer: "L1", entity: "Service", isDefault: true },
|
||||||
|
];
|
||||||
|
sessionStorage.setItem("dashboards", JSON.stringify(dashboards));
|
||||||
|
|
||||||
|
// layout: Tab with grandchildren + a non-tab widget
|
||||||
|
const layout = [
|
||||||
|
{
|
||||||
|
type: "Tab",
|
||||||
|
id: "tab0",
|
||||||
|
y: 10,
|
||||||
|
h: 20,
|
||||||
|
children: [
|
||||||
|
{ name: "Tab1", children: [] },
|
||||||
|
{
|
||||||
|
name: "Tab2",
|
||||||
|
children: [
|
||||||
|
{ type: "Card", id: "tab0-1-0", y: 5, h: 10 },
|
||||||
|
{ type: "Line", id: "tab0-1-1", y: 6, h: 12 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "Line", id: "wid1", y: 2, h: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const setWidget = vi.fn();
|
||||||
|
const setActiveTabIndex = vi.fn();
|
||||||
|
mockDashboardStore = {
|
||||||
|
layout,
|
||||||
|
currentDashboard: { name: "B", layer: "L1", entity: "Service" },
|
||||||
|
setWidget,
|
||||||
|
setActiveTabIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { dashboard, widgets } = getDashboard({ name: "A", layer: "L1", entity: "Service" }, ConfigFieldTypes.NAME);
|
||||||
|
|
||||||
|
expect(dashboard).toEqual(dashboards[0]);
|
||||||
|
// widgets should include: Tab itself + grandchildren (2) + non-tab (1) = 4
|
||||||
|
expect(widgets).toHaveLength(4);
|
||||||
|
expect(widgets.map((w: any) => w.id)).toEqual(["tab0", "tab0-1-0", "tab0-1-1", "wid1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects dashboard by ISDEFAULT using currentDashboard when param omitted", () => {
|
||||||
|
const dashboards = [
|
||||||
|
{ name: "A", layer: "L1", entity: "Service", isDefault: false },
|
||||||
|
{ name: "B", layer: "L1", entity: "Service", isDefault: true },
|
||||||
|
];
|
||||||
|
sessionStorage.setItem("dashboards", JSON.stringify(dashboards));
|
||||||
|
|
||||||
|
mockDashboardStore = {
|
||||||
|
layout: [],
|
||||||
|
currentDashboard: { name: "C", layer: "L1", entity: "Service" },
|
||||||
|
setWidget: vi.fn(),
|
||||||
|
setActiveTabIndex: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { dashboard } = getDashboard(undefined, ConfigFieldTypes.ISDEFAULT);
|
||||||
|
expect(dashboard).toEqual(dashboards[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("associationWidget: non-tab widget scrolls main container and sets widget", () => {
|
||||||
|
const layout = [{ type: "Line", id: "wid1", y: 3, h: 7 }];
|
||||||
|
const setWidget = vi.fn();
|
||||||
|
const setActiveTabIndex = vi.fn();
|
||||||
|
mockDashboardStore = { layout, currentDashboard: {}, setWidget, setActiveTabIndex };
|
||||||
|
|
||||||
|
const { associationWidget } = getDashboard({ name: "A", layer: "L1", entity: "Service" }, ConfigFieldTypes.NAME);
|
||||||
|
|
||||||
|
associationWidget("src", { dataIndex: 1 }, "Line");
|
||||||
|
|
||||||
|
expect(setWidget).toHaveBeenCalledTimes(1);
|
||||||
|
const arg = setWidget.mock.calls[0][0];
|
||||||
|
expect(arg.filters).toEqual({ dataIndex: 1 });
|
||||||
|
expect(arg.id).toBe("wid1");
|
||||||
|
|
||||||
|
// No tab index change for non-tab widget
|
||||||
|
expect(setActiveTabIndex).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const main = document.querySelector(".ds-main") as HTMLElement;
|
||||||
|
expect(main.scrollTop).toBe(3 * 10 + 7 * 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("associationWidget: tab child widget sets active tab and scrolls both containers", () => {
|
||||||
|
const layout = [
|
||||||
|
{
|
||||||
|
type: "Tab",
|
||||||
|
id: "tab0",
|
||||||
|
y: 10,
|
||||||
|
h: 20,
|
||||||
|
children: [
|
||||||
|
{ name: "Tab1", children: [] },
|
||||||
|
{ name: "Tab2", children: [{ type: "Card", id: "tab0-1-0", y: 5, h: 10 }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const setWidget = vi.fn();
|
||||||
|
const setActiveTabIndex = vi.fn();
|
||||||
|
mockDashboardStore = { layout, currentDashboard: {}, setWidget, setActiveTabIndex };
|
||||||
|
|
||||||
|
const { associationWidget } = getDashboard({ name: "A", layer: "L1", entity: "Service" }, ConfigFieldTypes.NAME);
|
||||||
|
|
||||||
|
associationWidget("tab0-0-9", { isRange: true }, "Card");
|
||||||
|
|
||||||
|
// set widget called with merged filters
|
||||||
|
expect(setWidget).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setWidget.mock.calls[0][0].id).toBe("tab0-1-0");
|
||||||
|
expect(setWidget.mock.calls[0][0].filters).toEqual({ isRange: true });
|
||||||
|
|
||||||
|
// active tab index set to 1 (from target id tab0-1-0)
|
||||||
|
expect(setActiveTabIndex).toHaveBeenCalledWith(1);
|
||||||
|
|
||||||
|
const main = document.querySelector(".ds-main") as HTMLElement;
|
||||||
|
const tab = document.querySelector(".tab-layout") as HTMLElement;
|
||||||
|
expect(main.scrollTop).toBe(10 * 10 + 20 * 5); // scroll to Tab container
|
||||||
|
expect(tab.scrollTop).toBe(5 * 10 + 10 * 5); // scroll to widget inside tab layout
|
||||||
|
});
|
||||||
|
|
||||||
|
it("associationWidget: when widget is missing, shows info message", () => {
|
||||||
|
const layout: any[] = [{ type: "Line", id: "wid1", y: 0, h: 0 }];
|
||||||
|
mockDashboardStore = { layout, currentDashboard: {}, setWidget: vi.fn(), setActiveTabIndex: vi.fn() };
|
||||||
|
|
||||||
|
const { associationWidget } = getDashboard({ name: "A", layer: "L1", entity: "Service" }, ConfigFieldTypes.NAME);
|
||||||
|
associationWidget("src", {}, "Table");
|
||||||
|
|
||||||
|
expect(ElMessage.info as any).toHaveBeenCalledTimes(1);
|
||||||
|
expect((ElMessage.info as any).mock.calls[0][0]).toContain("Table");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("associationWidget: if sourceId equals target widget id, only sets widget and returns early", () => {
|
||||||
|
const layout = [{ type: "Line", id: "wid1", y: 3, h: 7 }];
|
||||||
|
const setWidget = vi.fn();
|
||||||
|
const setActiveTabIndex = vi.fn();
|
||||||
|
mockDashboardStore = { layout, currentDashboard: {}, setWidget, setActiveTabIndex };
|
||||||
|
|
||||||
|
const { associationWidget } = getDashboard({ name: "A", layer: "L1", entity: "Service" }, ConfigFieldTypes.NAME);
|
||||||
|
|
||||||
|
associationWidget("wid1", { sourceId: "test" }, "Line");
|
||||||
|
|
||||||
|
expect(setWidget).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setActiveTabIndex).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const main = document.querySelector(".ds-main") as HTMLElement;
|
||||||
|
const tab = document.querySelector(".tab-layout") as HTMLElement;
|
||||||
|
// Early return: scroll positions unchanged (default 0)
|
||||||
|
expect(main.scrollTop).toBe(0);
|
||||||
|
expect(tab.scrollTop).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
151
src/hooks/__tests__/useEcharts.spec.ts
Normal file
151
src/hooks/__tests__/useEcharts.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { ref, nextTick, reactive } from "vue";
|
||||||
|
import { useECharts } from "../useEcharts";
|
||||||
|
import { Themes } from "@/constants/data";
|
||||||
|
|
||||||
|
// echarts mock
|
||||||
|
const initMock = vi.fn();
|
||||||
|
const instanceFactory = () => ({
|
||||||
|
setOption: vi.fn(),
|
||||||
|
clear: vi.fn(),
|
||||||
|
dispose: vi.fn(),
|
||||||
|
resize: vi.fn(),
|
||||||
|
});
|
||||||
|
let lastInstance: any;
|
||||||
|
vi.mock("@/utils/echarts", () => ({
|
||||||
|
default: {
|
||||||
|
init: vi.fn((el: any, theme: string) => {
|
||||||
|
lastInstance = instanceFactory();
|
||||||
|
(initMock as any).calls ??= [];
|
||||||
|
initMock(el, theme);
|
||||||
|
return lastInstance;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// reactive app store mock; we'll reassign per test
|
||||||
|
let appStoreMock: any;
|
||||||
|
vi.mock("@/store/modules/app", () => ({
|
||||||
|
useAppStoreWithOut: () => appStoreMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// provide useBreakpoint to avoid accessing undefined globals
|
||||||
|
vi.mock("../useBreakpoint", () => ({
|
||||||
|
useBreakpoint: () => ({ widthRef: 2000, screenEnum: { MD: 768 } }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function makeDiv(width = 300, height = 200) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
Object.defineProperty(div, "offsetHeight", { value: height, configurable: true });
|
||||||
|
div.getBoundingClientRect = () => ({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: width,
|
||||||
|
bottom: height,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
toJSON() {},
|
||||||
|
});
|
||||||
|
document.body.appendChild(div);
|
||||||
|
return div as HTMLDivElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useECharts", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.clearAllTimers();
|
||||||
|
appStoreMock = reactive({ theme: "default" });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes and sets options (light mode)", async () => {
|
||||||
|
const el = makeDiv();
|
||||||
|
const elRef = ref<HTMLDivElement>(el);
|
||||||
|
const { setOptions } = useECharts(elRef, "dark");
|
||||||
|
|
||||||
|
const options: any = { title: { text: "Hello" } };
|
||||||
|
setOptions(options);
|
||||||
|
|
||||||
|
// flush nextTick and the internal 30ms timeout
|
||||||
|
await nextTick();
|
||||||
|
vi.advanceTimersByTime(35);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(initMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(initMock).toHaveBeenCalledWith(el, Themes.Light);
|
||||||
|
expect(lastInstance.clear).toHaveBeenCalledTimes(1);
|
||||||
|
expect(lastInstance.setOption).toHaveBeenCalledTimes(1);
|
||||||
|
expect(lastInstance.setOption.mock.calls[0][0]).toStrictEqual(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles window resize via debounced listener", async () => {
|
||||||
|
const el = makeDiv();
|
||||||
|
const elRef = ref<HTMLDivElement>(el);
|
||||||
|
const { setOptions } = useECharts(elRef, "dark");
|
||||||
|
setOptions({} as any);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
vi.advanceTimersByTime(35);
|
||||||
|
|
||||||
|
// trigger resize event
|
||||||
|
window.dispatchEvent(new Event("resize"));
|
||||||
|
|
||||||
|
// two layers of debounce: 80 (listener) + 200 (resizeFn)
|
||||||
|
vi.advanceTimersByTime(300);
|
||||||
|
expect(lastInstance.resize).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies dark theme background and uses provided theme string", async () => {
|
||||||
|
appStoreMock.theme = Themes.Dark;
|
||||||
|
const el = makeDiv();
|
||||||
|
const elRef = ref<HTMLDivElement>(el);
|
||||||
|
const { setOptions } = useECharts(elRef, "dark");
|
||||||
|
|
||||||
|
const options: any = { title: { text: "Dark" } };
|
||||||
|
setOptions(options);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
vi.advanceTimersByTime(35);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(initMock).toHaveBeenCalledWith(el, "dark");
|
||||||
|
expect(lastInstance.setOption).toHaveBeenCalledTimes(1);
|
||||||
|
const passed = lastInstance.setOption.mock.calls[0][0];
|
||||||
|
expect(passed).toMatchObject({ backgroundColor: "transparent", title: { text: "Dark" } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getInstance initializes chart on demand", () => {
|
||||||
|
const el = makeDiv();
|
||||||
|
const elRef = ref<HTMLDivElement>(el);
|
||||||
|
const { getInstance } = useECharts(elRef, "dark");
|
||||||
|
|
||||||
|
const inst = getInstance();
|
||||||
|
expect(initMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(inst).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
138
src/hooks/__tests__/useEventListener.spec.ts
Normal file
138
src/hooks/__tests__/useEventListener.spec.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { useEventListener } from "../useEventListener";
|
||||||
|
|
||||||
|
describe("useEventListener", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.clearAllTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds listener to window and invokes handler (no wait)", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
|
||||||
|
const { removeEvent } = useEventListener({
|
||||||
|
name: "click",
|
||||||
|
listener: handler,
|
||||||
|
// wait = 0 ensures realHandler is the raw listener (no debounce/throttle)
|
||||||
|
wait: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// removing should stop further calls
|
||||||
|
removeEvent();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds listener to a custom element and removes via removeEvent", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const div = document.createElement("div");
|
||||||
|
|
||||||
|
const { removeEvent } = useEventListener({
|
||||||
|
el: div,
|
||||||
|
name: "custom",
|
||||||
|
listener: handler,
|
||||||
|
wait: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
div.dispatchEvent(new Event("custom"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
removeEvent();
|
||||||
|
div.dispatchEvent(new Event("custom"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects debounce when wait > 0", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
|
||||||
|
useEventListener({
|
||||||
|
name: "scroll",
|
||||||
|
listener: handler,
|
||||||
|
isDebounce: true,
|
||||||
|
wait: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fire multiple events rapidly
|
||||||
|
window.dispatchEvent(new Event("scroll"));
|
||||||
|
window.dispatchEvent(new Event("scroll"));
|
||||||
|
window.dispatchEvent(new Event("scroll"));
|
||||||
|
|
||||||
|
// Before debounce delay: not called
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// After debounce delay: called once
|
||||||
|
vi.advanceTimersByTime(100);
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects throttle when wait > 0 (leading true, trailing false by default)", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
|
||||||
|
useEventListener({
|
||||||
|
name: "mousemove",
|
||||||
|
listener: handler,
|
||||||
|
isDebounce: false,
|
||||||
|
wait: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// First call should fire immediately (leading)
|
||||||
|
window.dispatchEvent(new Event("mousemove"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Rapid subsequent event within the window should be throttled
|
||||||
|
vi.advanceTimersByTime(10);
|
||||||
|
window.dispatchEvent(new Event("mousemove"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// After the throttle window passes, still no trailing call by default
|
||||||
|
vi.advanceTimersByTime(100);
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Next event after window should invoke again
|
||||||
|
window.dispatchEvent(new Event("mousemove"));
|
||||||
|
expect(handler).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports addEventListener options (once)", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
|
||||||
|
useEventListener({
|
||||||
|
name: "keyup",
|
||||||
|
listener: handler,
|
||||||
|
options: { once: true },
|
||||||
|
wait: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event("keyup"));
|
||||||
|
window.dispatchEvent(new Event("keyup"));
|
||||||
|
|
||||||
|
// Because of once: true the handler should run only once
|
||||||
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
387
src/hooks/__tests__/useExpressionsProcessor.spec.ts
Normal file
387
src/hooks/__tests__/useExpressionsProcessor.spec.ts
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import {
|
||||||
|
useDashboardQueryProcessor,
|
||||||
|
useExpressionsQueryPodsMetrics,
|
||||||
|
useQueryTopologyExpressionsProcessor,
|
||||||
|
} from "../useExpressionsProcessor";
|
||||||
|
import { ExpressionResultType } from "@/views/dashboard/data";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
// Mock stores
|
||||||
|
let mockDashboardStore: any;
|
||||||
|
let mockTopologyStore: any;
|
||||||
|
let mockSelectorStore: any;
|
||||||
|
let mockAppStore: any;
|
||||||
|
|
||||||
|
vi.mock("@/store/modules/dashboard", () => ({
|
||||||
|
useDashboardStore: () => mockDashboardStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/store/modules/topology", () => ({
|
||||||
|
useTopologyStore: () => mockTopologyStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/store/modules/selectors", () => ({
|
||||||
|
useSelectorStore: () => mockSelectorStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/store/modules/app", () => ({
|
||||||
|
useAppStoreWithOut: () => mockAppStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock ElMessage
|
||||||
|
vi.mock("element-plus", () => ({
|
||||||
|
ElMessage: { error: vi.fn() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("useExpressionsProcessor", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockDashboardStore = {
|
||||||
|
entity: "Service",
|
||||||
|
fetchMetricValue: vi.fn(),
|
||||||
|
};
|
||||||
|
mockTopologyStore = {
|
||||||
|
getTopologyExpressionValue: vi.fn(),
|
||||||
|
};
|
||||||
|
mockSelectorStore = {
|
||||||
|
currentService: { value: "test-service", normal: true },
|
||||||
|
currentDestService: { value: "dest-service", normal: true },
|
||||||
|
currentPod: { value: "test-pod" },
|
||||||
|
currentDestPod: { value: "dest-pod" },
|
||||||
|
currentProcess: { value: "test-process" },
|
||||||
|
currentDestProcess: { value: "dest-process" },
|
||||||
|
};
|
||||||
|
mockAppStore = {
|
||||||
|
durationTime: { start: "2023-01-01", end: "2023-01-02", step: "HOUR" },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useDashboardQueryProcessor", () => {
|
||||||
|
it("returns empty result when no configs provided", async () => {
|
||||||
|
const result = await useDashboardQueryProcessor([]);
|
||||||
|
expect(result).toEqual({ 0: { source: {}, tips: [], typesOfMQE: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty result when config has no metrics", async () => {
|
||||||
|
const configs = [{ id: "1", metrics: [] }];
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
expect(result).toEqual({ 0: { source: {}, tips: [], typesOfMQE: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty result when no currentService and entity is not All", async () => {
|
||||||
|
mockSelectorStore.currentService = null;
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
expect(result).toEqual({ 0: { source: {}, tips: [], typesOfMQE: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty result when entity is relation but no currentDestService", async () => {
|
||||||
|
mockDashboardStore.entity = "ServiceRelation";
|
||||||
|
mockSelectorStore.currentDestService = null;
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
expect(result).toEqual({ 0: { source: {}, tips: [], typesOfMQE: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("processes single config successfully", async () => {
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
type: ExpressionResultType.SINGLE_VALUE,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [{ key: "service", value: "test" }] },
|
||||||
|
values: [{ value: "100" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
"1": {
|
||||||
|
source: { "metric1, service=test": ["100"] },
|
||||||
|
tips: [""],
|
||||||
|
typesOfMQE: [ExpressionResultType.SINGLE_VALUE],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles errors in response", async () => {
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const mockResponse = { errors: "Query failed" };
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
|
||||||
|
expect(ElMessage.error).toHaveBeenCalledWith("Query failed");
|
||||||
|
expect(result).toEqual({ 0: { source: {}, tips: [], typesOfMQE: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles TIME_SERIES_VALUES type", async () => {
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
type: ExpressionResultType.TIME_SERIES_VALUES,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [{ key: "service", value: "test" }] },
|
||||||
|
values: [{ value: "100" }, { value: "200" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
|
||||||
|
expect((result as any)["1"].source).toEqual({ "metric1, service=test": ["100", "200"] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles RECORD_LIST type", async () => {
|
||||||
|
const configs = [{ id: "1", metrics: ["metric1"] }];
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
type: ExpressionResultType.RECORD_LIST,
|
||||||
|
results: [{ values: [{ value: "record1" }, { value: "record2" }] }],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useDashboardQueryProcessor(configs);
|
||||||
|
|
||||||
|
expect((result as any)["1"].source).toEqual({ metric1: [{ value: "record1" }, { value: "record2" }] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useExpressionsQueryPodsMetrics", () => {
|
||||||
|
const mockPods = [
|
||||||
|
{ label: "pod1", normal: true, value: "pod1" },
|
||||||
|
{ label: "pod2", normal: false, value: "pod2" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockConfig = {
|
||||||
|
expressions: ["expression1", "expression2"],
|
||||||
|
subExpressions: ["sub1", "sub2"],
|
||||||
|
metricConfig: [{ label: "config1" }, { label: "config2" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
it("returns empty result when no expressions", async () => {
|
||||||
|
const config = { expressions: [], subExpressions: [], metricConfig: [] };
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue({ data: {} });
|
||||||
|
const result = await useExpressionsQueryPodsMetrics(mockPods, config, "Service");
|
||||||
|
expect(result).toEqual({
|
||||||
|
data: [
|
||||||
|
{ label: "pod1", normal: true, value: "pod1" },
|
||||||
|
{ label: "pod2", normal: false, value: "pod2" },
|
||||||
|
],
|
||||||
|
expressionsTips: [],
|
||||||
|
subExpressionsTips: [],
|
||||||
|
names: [],
|
||||||
|
subNames: [],
|
||||||
|
metricConfigArr: [],
|
||||||
|
metricTypesArr: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("processes pods metrics successfully", async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
type: ExpressionResultType.SINGLE_VALUE,
|
||||||
|
results: [{ values: [{ value: "100" }] }],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
expression01: {
|
||||||
|
type: ExpressionResultType.SINGLE_VALUE,
|
||||||
|
results: [{ values: [{ value: "200" }] }],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
subexpression00: {
|
||||||
|
results: [{ values: [{ value: "50" }] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useExpressionsQueryPodsMetrics(mockPods, mockConfig, "Service");
|
||||||
|
|
||||||
|
expect(result.data).toHaveLength(2);
|
||||||
|
expect(result.expressionsTips).toHaveLength(3);
|
||||||
|
expect(result.subExpressionsTips).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip("handles errors in response", async () => {
|
||||||
|
// This test is skipped because the original function has a bug where it returns {}
|
||||||
|
// but the main function expects item.data to be iterable
|
||||||
|
// The error handling in the original code needs to be fixed
|
||||||
|
const mockResponse = { errors: "Query failed" };
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
await useExpressionsQueryPodsMetrics(mockPods, mockConfig, "Service");
|
||||||
|
expect(ElMessage.error).toHaveBeenCalledWith("Query failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple results with labels", async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
type: ExpressionResultType.SINGLE_VALUE,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [{ key: "service", value: "service1" }] },
|
||||||
|
values: [{ value: "100" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: { labels: [{ key: "service", value: "service2" }] },
|
||||||
|
values: [{ value: "200" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockDashboardStore.fetchMetricValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await useExpressionsQueryPodsMetrics(mockPods, mockConfig, "Service");
|
||||||
|
|
||||||
|
expect(result.data).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useQueryTopologyExpressionsProcessor", () => {
|
||||||
|
const mockMetrics = ["metric1", "metric2"];
|
||||||
|
const mockInstances = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
sourceObj: { serviceName: "service1", normal: true },
|
||||||
|
targetObj: { serviceName: "service2", normal: false },
|
||||||
|
source: "source1",
|
||||||
|
target: "target1",
|
||||||
|
detectPoints: ["CLIENT"],
|
||||||
|
sourceComponents: [],
|
||||||
|
targetComponents: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
serviceName: "service3",
|
||||||
|
normal: true,
|
||||||
|
name: "service3",
|
||||||
|
},
|
||||||
|
] as any;
|
||||||
|
|
||||||
|
it("returns getMetrics function", () => {
|
||||||
|
const result = useQueryTopologyExpressionsProcessor(mockMetrics, mockInstances);
|
||||||
|
expect(typeof result.getMetrics).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("processes topology expressions successfully", async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
results: [{ values: [{ value: "100" }] }],
|
||||||
|
},
|
||||||
|
expression01: {
|
||||||
|
results: [{ values: [{ value: "200" }] }],
|
||||||
|
},
|
||||||
|
expression10: {
|
||||||
|
results: [{ values: [{ value: "100" }] }],
|
||||||
|
},
|
||||||
|
expression11: {
|
||||||
|
results: [{ values: [{ value: "200" }] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockTopologyStore.getTopologyExpressionValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const { getMetrics } = useQueryTopologyExpressionsProcessor(mockMetrics, mockInstances);
|
||||||
|
const result = await getMetrics();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
metric1: {
|
||||||
|
values: [
|
||||||
|
{ value: "100", id: "1" },
|
||||||
|
{ value: "100", id: "2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
metric2: {
|
||||||
|
values: [
|
||||||
|
{ value: "200", id: "1" },
|
||||||
|
{ value: "200", id: "2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles errors in topology response", async () => {
|
||||||
|
const mockResponse = { errors: "Topology query failed" };
|
||||||
|
mockTopologyStore.getTopologyExpressionValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const { getMetrics } = useQueryTopologyExpressionsProcessor(mockMetrics, mockInstances);
|
||||||
|
const result = await getMetrics();
|
||||||
|
|
||||||
|
expect(ElMessage.error).toHaveBeenCalledWith("Topology query failed");
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty metrics array", async () => {
|
||||||
|
mockTopologyStore.getTopologyExpressionValue.mockResolvedValue({ data: {} });
|
||||||
|
const { getMetrics } = useQueryTopologyExpressionsProcessor([], mockInstances);
|
||||||
|
const result = await getMetrics();
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty instances array", async () => {
|
||||||
|
mockTopologyStore.getTopologyExpressionValue.mockResolvedValue({ data: {} });
|
||||||
|
const { getMetrics } = useQueryTopologyExpressionsProcessor(mockMetrics, []);
|
||||||
|
const result = await getMetrics();
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("processes different entity types correctly", async () => {
|
||||||
|
mockDashboardStore.entity = "ServiceInstance";
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
expression00: {
|
||||||
|
results: [{ values: [{ value: "100" }] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockTopologyStore.getTopologyExpressionValue.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const { getMetrics } = useQueryTopologyExpressionsProcessor(mockMetrics, mockInstances);
|
||||||
|
const result = await getMetrics();
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
433
src/hooks/__tests__/useLegendProcessor.spec.ts
Normal file
433
src/hooks/__tests__/useLegendProcessor.spec.ts
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import useLegendProcess from "../useLegendProcessor";
|
||||||
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
import { Themes } from "@/constants/data";
|
||||||
|
import { DarkChartColors, LightChartColors } from "../data";
|
||||||
|
import type { LegendOptions } from "@/types/dashboard";
|
||||||
|
|
||||||
|
// Mock the store
|
||||||
|
vi.mock("@/store/modules/app", () => ({
|
||||||
|
useAppStoreWithOut: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("useLegendProcess hook", () => {
|
||||||
|
const mockAppStore = {
|
||||||
|
theme: Themes.Light,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
(useAppStoreWithOut as any).mockReturnValue(mockAppStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isRight property", () => {
|
||||||
|
it("should return false when legend is undefined", () => {
|
||||||
|
const { isRight } = useLegendProcess();
|
||||||
|
expect(isRight).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when legend.toTheRight is false", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { isRight } = useLegendProcess(legend);
|
||||||
|
expect(isRight).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when legend.toTheRight is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: true,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { isRight } = useLegendProcess(legend);
|
||||||
|
expect(isRight).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("showEchartsLegend function", () => {
|
||||||
|
it("should return false when legend.show is false", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: false,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { showEchartsLegend } = useLegendProcess(legend);
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when legend.asTable is true and legend.show is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: true,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { showEchartsLegend } = useLegendProcess(legend);
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when legend.show is true and asTable is false", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { showEchartsLegend } = useLegendProcess(legend);
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when keys length is 1", () => {
|
||||||
|
const { showEchartsLegend } = useLegendProcess();
|
||||||
|
expect(showEchartsLegend(["singleKey"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when legend.asTable is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: true,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { showEchartsLegend } = useLegendProcess(legend);
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when legend.asSelector is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: undefined as any,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: true,
|
||||||
|
};
|
||||||
|
const { showEchartsLegend } = useLegendProcess(legend);
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when no legend options and multiple keys", () => {
|
||||||
|
const { showEchartsLegend } = useLegendProcess();
|
||||||
|
expect(showEchartsLegend(["key1", "key2", "key3"])).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("aggregations function", () => {
|
||||||
|
const mockData = {
|
||||||
|
service1: [10, 20, 30, 40, 50],
|
||||||
|
service2: [5, 15, 25, 35, 45],
|
||||||
|
};
|
||||||
|
const mockIntervalTime = ["2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05"];
|
||||||
|
|
||||||
|
it("should return empty source and headers when data is empty", () => {
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result = aggregations({}, mockIntervalTime);
|
||||||
|
expect(result.source).toEqual([]);
|
||||||
|
expect(result.headers).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty source and headers when data is null", () => {
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result = aggregations(null as any, mockIntervalTime);
|
||||||
|
expect(result.source).toEqual([]);
|
||||||
|
expect(result.headers).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out non-array data", () => {
|
||||||
|
const invalidData: { [key: string]: number[] } = {
|
||||||
|
service1: [10, 20, 30],
|
||||||
|
service2: "not an array" as any,
|
||||||
|
service3: [],
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result = aggregations(invalidData, mockIntervalTime);
|
||||||
|
expect(result.source).toHaveLength(1);
|
||||||
|
expect(result.source[0].name).toBe("service1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out empty arrays", () => {
|
||||||
|
const dataWithEmptyArrays = {
|
||||||
|
service1: [10, 20, 30],
|
||||||
|
service2: [],
|
||||||
|
service3: [5, 15, 25],
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result = aggregations(dataWithEmptyArrays, mockIntervalTime);
|
||||||
|
expect(result.source).toHaveLength(2);
|
||||||
|
expect(result.source.map((item: any) => item.name)).toEqual(["service1", "service3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create topN with sorted values", () => {
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result: any = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
expect(result.source).toHaveLength(2);
|
||||||
|
expect(result.source[0].name).toBe("service1");
|
||||||
|
expect(result.source[0].topN).toHaveLength(5);
|
||||||
|
expect(result.source[0].topN[0].value).toBe(50); // Highest value first
|
||||||
|
expect(result.source[0].topN[4].value).toBe(10); // Lowest value last
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should limit topN to 10 items", () => {
|
||||||
|
const largeData = {
|
||||||
|
service1: Array.from({ length: 15 }, (_, i) => i + 1),
|
||||||
|
};
|
||||||
|
const largeIntervalTime = Array.from({ length: 15 }, (_, i) => `2023-01-${String(i + 1).padStart(2, "0")}`);
|
||||||
|
|
||||||
|
const { aggregations } = useLegendProcess();
|
||||||
|
const result = aggregations(largeData, largeIntervalTime);
|
||||||
|
|
||||||
|
expect(result.source[0].topN).toHaveLength(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include min when legend.min is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: true,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
expect(result.source[0].min).toBe("10.00");
|
||||||
|
expect(result.headers).toContainEqual({ value: "min", label: "Min" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include max when legend.max is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: true,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
expect(result.source[0].max).toBe("50.00");
|
||||||
|
expect(result.headers).toContainEqual({ value: "max", label: "Max" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include mean when legend.mean is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: false,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: true,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
// Mean of [10, 20, 30, 40, 50] = 30
|
||||||
|
expect(result.source[0].mean).toBe("30.0000");
|
||||||
|
expect(result.headers).toContainEqual({ value: "mean", label: "Mean" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include total when legend.total is true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: true,
|
||||||
|
min: false,
|
||||||
|
max: false,
|
||||||
|
mean: false,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
// Total of [10, 20, 30, 40, 50] = 150
|
||||||
|
expect(result.source[0].total).toBe("150.00");
|
||||||
|
expect(result.headers).toContainEqual({ value: "total", label: "Total" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include all statistics when all legend options are true", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: true,
|
||||||
|
min: true,
|
||||||
|
max: true,
|
||||||
|
mean: true,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
expect(result.source[0].min).toBe("10.00");
|
||||||
|
expect(result.source[0].max).toBe("50.00");
|
||||||
|
expect(result.source[0].mean).toBe("30.0000");
|
||||||
|
expect(result.source[0].total).toBe("150.00");
|
||||||
|
expect(result.headers).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only add headers once for the first item", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: true,
|
||||||
|
min: true,
|
||||||
|
max: true,
|
||||||
|
mean: true,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: false,
|
||||||
|
width: 100,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
const { aggregations } = useLegendProcess(legend);
|
||||||
|
const result = aggregations(mockData, mockIntervalTime);
|
||||||
|
|
||||||
|
// Should have 4 headers (min, max, mean, total) even with 2 data items
|
||||||
|
expect(result.headers).toHaveLength(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("chartColors function", () => {
|
||||||
|
it("should return light chart colors when theme is light", () => {
|
||||||
|
(useAppStoreWithOut as any).mockReturnValue({ theme: Themes.Light });
|
||||||
|
const { chartColors } = useLegendProcess();
|
||||||
|
expect(chartColors()).toBe(LightChartColors);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return dark chart colors when theme is dark", () => {
|
||||||
|
(useAppStoreWithOut as any).mockReturnValue({ theme: Themes.Dark });
|
||||||
|
const { chartColors } = useLegendProcess();
|
||||||
|
expect(chartColors()).toBe(DarkChartColors);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call useAppStoreWithOut", () => {
|
||||||
|
const { chartColors } = useLegendProcess();
|
||||||
|
chartColors();
|
||||||
|
expect(useAppStoreWithOut).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("integration tests", () => {
|
||||||
|
it("should work with complete legend configuration", () => {
|
||||||
|
const legend: LegendOptions = {
|
||||||
|
show: true,
|
||||||
|
total: true,
|
||||||
|
min: true,
|
||||||
|
max: true,
|
||||||
|
mean: true,
|
||||||
|
asTable: false,
|
||||||
|
toTheRight: true,
|
||||||
|
width: 200,
|
||||||
|
asSelector: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isRight, showEchartsLegend, aggregations, chartColors } = useLegendProcess(legend);
|
||||||
|
|
||||||
|
// Test isRight
|
||||||
|
expect(isRight).toBe(true);
|
||||||
|
|
||||||
|
// Test showEchartsLegend
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(true);
|
||||||
|
|
||||||
|
// Test aggregations
|
||||||
|
const data = { service1: [10, 20, 30] };
|
||||||
|
const intervalTime = ["2023-01-01", "2023-01-02", "2023-01-03"];
|
||||||
|
const aggResult = aggregations(data, intervalTime);
|
||||||
|
expect(aggResult.source).toHaveLength(1);
|
||||||
|
expect(aggResult.headers).toHaveLength(4);
|
||||||
|
|
||||||
|
// Test chartColors
|
||||||
|
expect(chartColors()).toBe(LightChartColors);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work without legend configuration", () => {
|
||||||
|
const { isRight, showEchartsLegend, aggregations, chartColors } = useLegendProcess();
|
||||||
|
|
||||||
|
// Test isRight
|
||||||
|
expect(isRight).toBe(false);
|
||||||
|
|
||||||
|
// Test showEchartsLegend
|
||||||
|
expect(showEchartsLegend(["key1", "key2"])).toBe(true);
|
||||||
|
expect(showEchartsLegend(["singleKey"])).toBe(false);
|
||||||
|
|
||||||
|
// Test aggregations
|
||||||
|
const data = { service1: [10, 20, 30] };
|
||||||
|
const intervalTime = ["2023-01-01", "2023-01-02", "2023-01-03"];
|
||||||
|
const aggResult = aggregations(data, intervalTime);
|
||||||
|
expect(aggResult.source).toHaveLength(1);
|
||||||
|
expect(aggResult.headers).toHaveLength(0); // No legend options, so no headers
|
||||||
|
|
||||||
|
// Test chartColors
|
||||||
|
expect(chartColors()).toBe(LightChartColors);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
311
src/hooks/__tests__/useSnapshot.spec.ts
Normal file
311
src/hooks/__tests__/useSnapshot.spec.ts
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { useSnapshot } from "../useSnapshot";
|
||||||
|
import type { MetricsResults } from "@/types/dashboard";
|
||||||
|
|
||||||
|
// Helper function to create metric values with required properties
|
||||||
|
const createMetricValue = (value: string, name: string = "test") => ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
owner: null,
|
||||||
|
refId: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useSnapshot", () => {
|
||||||
|
describe("processResults", () => {
|
||||||
|
it("should process metrics without labels", () => {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
name: "cpu_usage",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [
|
||||||
|
{ name: "cpu_usage", value: "75.5", owner: null, refId: null },
|
||||||
|
{ name: "cpu_usage", value: "82.3", owner: null, refId: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "cpu_usage",
|
||||||
|
values: [{ values: [75.5, 82.3] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should process metrics with labels", () => {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
name: "memory_usage",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: {
|
||||||
|
labels: [{ key: "instance", value: "server-1" }],
|
||||||
|
},
|
||||||
|
values: [createMetricValue("45.2", "memory_usage")],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "memory_usage",
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: "memory_usage{instance=server-1}",
|
||||||
|
values: [45.2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should process metrics with multiple labels", () => {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
name: "http_requests",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: {
|
||||||
|
labels: [
|
||||||
|
{ key: "method", value: "GET" },
|
||||||
|
{ key: "status", value: "200" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
values: [createMetricValue("100", "http_requests"), createMetricValue("150", "http_requests")],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "http_requests",
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: "http_requests{method=GET},http_requests{status=200}",
|
||||||
|
values: [100, 150],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should process multiple metrics", () => {
|
||||||
|
const metrics: { name: string; results: MetricsResults[] }[] = [
|
||||||
|
{
|
||||||
|
name: "cpu_usage",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [{ value: "75.5", name: "cpu_usage", owner: null, refId: null }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "memory_usage",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: {
|
||||||
|
labels: [{ key: "instance", value: "server-1" }],
|
||||||
|
},
|
||||||
|
values: [{ value: "45.2", name: "memory_usage", owner: null, refId: null }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "cpu_usage",
|
||||||
|
values: [{ values: [75.5] }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "memory_usage",
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: "memory_usage{instance=server-1}",
|
||||||
|
values: [45.2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty values array", () => {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
name: "empty_metric",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "empty_metric",
|
||||||
|
values: [{ values: [] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty results array", () => {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
name: "no_results_metric",
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "no_results_metric",
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty metrics array", () => {
|
||||||
|
const metrics: { name: string; results: MetricsResults[] }[] = [];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle decimal values", () => {
|
||||||
|
const metrics: { name: string; results: MetricsResults[] }[] = [
|
||||||
|
{
|
||||||
|
name: "precision_metric",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [
|
||||||
|
{ value: "3.14159", name: "precision_metric", owner: null, refId: null },
|
||||||
|
{ value: "2.71828", name: "precision_metric", owner: null, refId: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "precision_metric",
|
||||||
|
values: [{ values: [3.14159, 2.71828] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle negative numbers", () => {
|
||||||
|
const metrics: { name: string; results: MetricsResults[] }[] = [
|
||||||
|
{
|
||||||
|
name: "negative_metric",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [
|
||||||
|
{ value: "-10", name: "negative_metric", owner: null, refId: null },
|
||||||
|
{ value: "-3.14", name: "negative_metric", owner: null, refId: null },
|
||||||
|
{ value: "0", name: "negative_metric", owner: null, refId: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "negative_metric",
|
||||||
|
values: [{ values: [-10, -3.14, 0] }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle mixed scenarios", () => {
|
||||||
|
const metrics: { name: string; results: MetricsResults[] }[] = [
|
||||||
|
{
|
||||||
|
name: "mixed_metric",
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
metric: { labels: [] },
|
||||||
|
values: [{ value: "100", name: "mixed_metric", owner: null, refId: null }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: {
|
||||||
|
labels: [{ key: "instance", value: "server-1" }],
|
||||||
|
},
|
||||||
|
values: [{ value: "200", name: "mixed_metric", owner: null, refId: null }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { processResults } = useSnapshot(metrics);
|
||||||
|
const result = processResults();
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
name: "mixed_metric",
|
||||||
|
values: [
|
||||||
|
{ values: [100] },
|
||||||
|
{
|
||||||
|
name: "mixed_metric{instance=server-1}",
|
||||||
|
values: [200],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
360
src/hooks/__tests__/useTimeout.spec.ts
Normal file
360
src/hooks/__tests__/useTimeout.spec.ts
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { nextTick } from "vue";
|
||||||
|
import { useTimeoutFn, useTimeoutRef } from "../useTimeout";
|
||||||
|
|
||||||
|
describe("useTimeout", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.clearAllTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useTimeoutRef", () => {
|
||||||
|
it("should initialize with readyRef as false", () => {
|
||||||
|
const { readyRef } = useTimeoutRef(1000);
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set readyRef to true after timeout", async () => {
|
||||||
|
const { readyRef } = useTimeoutRef(1000);
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start timer immediately", () => {
|
||||||
|
const { readyRef } = useTimeoutRef(500);
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide stop function that clears timer", () => {
|
||||||
|
const { readyRef, stop } = useTimeoutRef(1000);
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
stop();
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide start function that restarts timer", () => {
|
||||||
|
const { readyRef, start } = useTimeoutRef(1000);
|
||||||
|
|
||||||
|
// Wait for initial timer
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
|
||||||
|
// Reset and restart
|
||||||
|
start();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple start calls", () => {
|
||||||
|
const { readyRef, start } = useTimeoutRef(1000);
|
||||||
|
|
||||||
|
// Call start multiple times
|
||||||
|
start();
|
||||||
|
start();
|
||||||
|
start();
|
||||||
|
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle zero timeout", () => {
|
||||||
|
const { readyRef } = useTimeoutRef(0);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(0);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle negative timeout", () => {
|
||||||
|
const { readyRef } = useTimeoutRef(-1000);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(0);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return all required functions and refs", () => {
|
||||||
|
const result = useTimeoutRef(1000);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("readyRef");
|
||||||
|
expect(result).toHaveProperty("stop");
|
||||||
|
expect(result).toHaveProperty("start");
|
||||||
|
expect(typeof result.stop).toBe("function");
|
||||||
|
expect(typeof result.start).toBe("function");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("useTimeoutFn", () => {
|
||||||
|
it("should call handle function after timeout when native is false", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
expect(mockHandle).not.toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call handle function immediately when native is true", () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef } = useTimeoutFn(mockHandle, 1000, true);
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call handle function immediately when native is false", () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
expect(mockHandle).not.toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide stop function that prevents handle execution", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef, stop } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
stop();
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).not.toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide start function that restarts timeout", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef, start } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
// Wait for initial timeout
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Reset and restart
|
||||||
|
start();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
// Wait a bit more for reactivity to update
|
||||||
|
await nextTick();
|
||||||
|
// The handle should be called at least once, and readyRef should be true
|
||||||
|
expect(mockHandle).toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle handle function that returns a value", async () => {
|
||||||
|
const mockHandle = vi.fn(() => "test result");
|
||||||
|
useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockHandle).toHaveReturnedWith("test result");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle handle function that throws an error", async () => {
|
||||||
|
const mockHandle = vi.fn(() => {
|
||||||
|
throw new Error("Test error");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use try-catch to handle the error that will be thrown by the watch
|
||||||
|
try {
|
||||||
|
useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
} catch (error) {
|
||||||
|
// The error is expected to be thrown by the watch function
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect((error as Error).message).toBe("Test error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with async handle function", async () => {
|
||||||
|
const mockHandle = vi.fn(async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
return "async result";
|
||||||
|
});
|
||||||
|
|
||||||
|
useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple timeout executions", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef, start } = useTimeoutFn(mockHandle, 500, false);
|
||||||
|
|
||||||
|
// First execution
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
await nextTick();
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Second execution
|
||||||
|
start();
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
expect(mockHandle).toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
|
||||||
|
// Third execution
|
||||||
|
start();
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
expect(mockHandle).toHaveBeenCalled();
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return all required functions and refs", () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const result = useTimeoutFn(mockHandle, 1000);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("readyRef");
|
||||||
|
expect(result).toHaveProperty("stop");
|
||||||
|
expect(result).toHaveProperty("start");
|
||||||
|
expect(typeof result.stop).toBe("function");
|
||||||
|
expect(typeof result.start).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error when handle is not a function", () => {
|
||||||
|
expect(() => {
|
||||||
|
useTimeoutFn("not a function" as any, 1000);
|
||||||
|
}).toThrow("handle is not Function!");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error when handle is null", () => {
|
||||||
|
expect(() => {
|
||||||
|
useTimeoutFn(null as any, 1000);
|
||||||
|
}).toThrow("handle is not Function!");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error when handle is undefined", () => {
|
||||||
|
expect(() => {
|
||||||
|
useTimeoutFn(undefined as any, 1000);
|
||||||
|
}).toThrow("handle is not Function!");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle zero wait time", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef } = useTimeoutFn(mockHandle, 0, false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(0);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle negative wait time", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef } = useTimeoutFn(mockHandle, -1000, false);
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(0);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Integration tests", () => {
|
||||||
|
it("should work together with Vue reactivity", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef, stop, start } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
expect(mockHandle).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// After timeout
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// After stop
|
||||||
|
stop();
|
||||||
|
expect(readyRef.value).toBe(false);
|
||||||
|
|
||||||
|
// After restart
|
||||||
|
start();
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
expect(mockHandle).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle rapid start/stop calls", async () => {
|
||||||
|
const mockHandle = vi.fn();
|
||||||
|
const { readyRef, stop, start } = useTimeoutFn(mockHandle, 1000, false);
|
||||||
|
|
||||||
|
// Rapid start/stop calls
|
||||||
|
start();
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(mockHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(readyRef.value).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -32,7 +32,8 @@ export default function useAssociateProcessor(props: AssociateProcessorProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const list = props.option.series[0].data.map((d: (number | string)[]) => d[0]);
|
const list = props.option.series[0].data.map((d: (number | string)[]) => d[0]);
|
||||||
if (!list.includes(props.filters.duration.endTime)) {
|
const { startTime, endTime } = props.filters.duration || {};
|
||||||
|
if (typeof endTime === "undefined" || !list.includes(endTime)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const markArea = {
|
const markArea = {
|
||||||
@@ -43,10 +44,10 @@ export default function useAssociateProcessor(props: AssociateProcessorProps) {
|
|||||||
data: [
|
data: [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
xAxis: props.filters.duration.startTime,
|
xAxis: startTime,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
xAxis: props.filters.duration.endTime,
|
xAxis: endTime,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@@ -30,15 +30,67 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
|||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||||
import type { Node, Call } from "@/types/topology";
|
import type { Node, Call } from "@/types/topology";
|
||||||
|
import type { ServiceWithGroup } from "@/views/dashboard/graphs/ServiceList.vue";
|
||||||
|
|
||||||
function chunkArray(array: any[], chunkSize: number) {
|
type AllPods = Instance | Endpoint | ServiceWithGroup;
|
||||||
|
/**
|
||||||
|
* Shape of a single execExpression GraphQL response entry.
|
||||||
|
*/
|
||||||
|
interface ExecExpressionResponse {
|
||||||
|
type?: ExpressionResultType | string;
|
||||||
|
error?: string;
|
||||||
|
results?: Array<{
|
||||||
|
metric?: { labels: Array<{ key: string; value: string }> };
|
||||||
|
values: Array<{ value: unknown }>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard widget config used for expression queries.
|
||||||
|
*/
|
||||||
|
export interface DashboardWidgetConfig {
|
||||||
|
id: string | number;
|
||||||
|
metrics: string[];
|
||||||
|
metricConfig?: MetricConfigOpt[];
|
||||||
|
subExpressions?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result shape of expressionsSource for a widget.
|
||||||
|
*/
|
||||||
|
export interface ExpressionsSourceResult {
|
||||||
|
source: Record<string, unknown>;
|
||||||
|
tips: string[];
|
||||||
|
typesOfMQE: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend pod entities with dynamic metric buckets that get attached during processing.
|
||||||
|
*/
|
||||||
|
interface MetricEntry {
|
||||||
|
values?: unknown[];
|
||||||
|
avg?: unknown | unknown[];
|
||||||
|
}
|
||||||
|
export type PodWithMetrics = (Instance | Endpoint | ServiceWithGroup) & { [metricName: string]: MetricEntry };
|
||||||
|
|
||||||
|
type ExpressionsPodsSourceResult = {
|
||||||
|
data: PodWithMetrics[];
|
||||||
|
names: string[];
|
||||||
|
subNames: string[];
|
||||||
|
metricConfigArr: MetricConfigOpt[];
|
||||||
|
metricTypesArr: string[];
|
||||||
|
expressionsTips: string[];
|
||||||
|
subExpressionsTips: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function chunkArray<T>(array: T[], chunkSize: number): T[][] {
|
||||||
if (chunkSize <= 0) {
|
if (chunkSize <= 0) {
|
||||||
return [array];
|
return [array];
|
||||||
}
|
}
|
||||||
if (chunkSize > array.length) {
|
if (chunkSize > array.length) {
|
||||||
return [array];
|
return [array];
|
||||||
}
|
}
|
||||||
const result = [];
|
const result: T[][] = [];
|
||||||
for (let i = 0; i < array.length; i += chunkSize) {
|
for (let i = 0; i < array.length; i += chunkSize) {
|
||||||
result.push(array.slice(i, i + chunkSize));
|
result.push(array.slice(i, i + chunkSize));
|
||||||
}
|
}
|
||||||
@@ -46,8 +98,8 @@ function chunkArray(array: any[], chunkSize: number) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
export async function useDashboardQueryProcessor(configList: DashboardWidgetConfig[]) {
|
||||||
function expressionsGraphql(config: Indexable, idx: number) {
|
function expressionsGraphql(config: DashboardWidgetConfig, idx: number) {
|
||||||
if (!(config.metrics && config.metrics[0])) {
|
if (!(config.metrics && config.metrics[0])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,7 +160,10 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
conditions,
|
conditions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function expressionsSource(config: Indexable, resp: { errors: string; data: Indexable | any }) {
|
function expressionsSource(
|
||||||
|
config: DashboardWidgetConfig,
|
||||||
|
resp: { errors: string; data: Record<string, ExecExpressionResponse> },
|
||||||
|
) {
|
||||||
if (resp.errors) {
|
if (resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
return { source: {}, tips: [], typesOfMQE: [] };
|
return { source: {}, tips: [], typesOfMQE: [] };
|
||||||
@@ -117,12 +172,8 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
ElMessage.error("The query is wrong");
|
ElMessage.error("The query is wrong");
|
||||||
return { source: {}, tips: [], typesOfMQE: [] };
|
return { source: {}, tips: [], typesOfMQE: [] };
|
||||||
}
|
}
|
||||||
if (resp.data.error) {
|
|
||||||
ElMessage.error(resp.data.error);
|
|
||||||
return { source: {}, tips: [], typesOfMQE: [] };
|
|
||||||
}
|
|
||||||
const tips: string[] = [];
|
const tips: string[] = [];
|
||||||
const source: Indexable<unknown> = {};
|
const source: Record<string, unknown> = {};
|
||||||
const keys = Object.keys(resp.data);
|
const keys = Object.keys(resp.data);
|
||||||
const typesOfMQE: string[] = [];
|
const typesOfMQE: string[] = [];
|
||||||
|
|
||||||
@@ -133,14 +184,24 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
const name = config.metrics[i];
|
const name = config.metrics[i];
|
||||||
const type = obj.type;
|
const type = obj.type;
|
||||||
|
|
||||||
tips.push(obj.error);
|
tips.push(obj.error || "");
|
||||||
typesOfMQE.push(type);
|
typesOfMQE.push(String(type ?? ""));
|
||||||
if (!obj.error) {
|
if (!obj.error) {
|
||||||
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
|
if (
|
||||||
|
[ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(
|
||||||
|
type as ExpressionResultType,
|
||||||
|
)
|
||||||
|
) {
|
||||||
for (const item of results) {
|
for (const item of results) {
|
||||||
let label =
|
let label: string = name;
|
||||||
item.metric &&
|
if (item.metric) {
|
||||||
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
|
const joined = item.metric.labels
|
||||||
|
.map((d: { key: string; value: string }) => `${d.key}=${d.value}`)
|
||||||
|
.join(",");
|
||||||
|
if (joined) {
|
||||||
|
label = joined;
|
||||||
|
}
|
||||||
|
}
|
||||||
const values = item.values.map((d: { value: unknown }) => d.value) || [];
|
const values = item.values.map((d: { value: unknown }) => d.value) || [];
|
||||||
if (results.length === 1) {
|
if (results.length === 1) {
|
||||||
// If the metrics label does not exist, use the configuration label or expression
|
// If the metrics label does not exist, use the configuration label or expression
|
||||||
@@ -149,7 +210,11 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
source[label] = values;
|
source[label] = values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
|
if (
|
||||||
|
([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(
|
||||||
|
String(type ?? ""),
|
||||||
|
)
|
||||||
|
) {
|
||||||
source[name] = results[0].values;
|
source[name] = results[0].values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +222,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
|
|
||||||
return { source, tips, typesOfMQE };
|
return { source, tips, typesOfMQE };
|
||||||
}
|
}
|
||||||
async function fetchMetrics(configArr: any) {
|
async function fetchMetrics(configArr: DashboardWidgetConfig[]) {
|
||||||
const appStore = useAppStoreWithOut();
|
const appStore = useAppStoreWithOut();
|
||||||
const variables: string[] = [`$duration: Duration!`];
|
const variables: string[] = [`$duration: Duration!`];
|
||||||
let fragments = "";
|
let fragments = "";
|
||||||
@@ -186,12 +251,12 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const pageData: Recordable = {};
|
const pageData: Record<string | number, ExpressionsSourceResult> = {};
|
||||||
|
|
||||||
for (let i = 0; i < configArr.length; i++) {
|
for (let i = 0; i < configArr.length; i++) {
|
||||||
const resp: any = {};
|
const resp: Record<string, ExecExpressionResponse> = {};
|
||||||
for (let m = 0; m < configArr[i].metrics.length; m++) {
|
for (let m = 0; m < configArr[i].metrics.length; m++) {
|
||||||
resp[`expression${i}${m}`] = json.data[`expression${i}${m}`];
|
resp[`expression${i}${m}`] = json.data[`expression${i}${m}`] as ExecExpressionResponse;
|
||||||
}
|
}
|
||||||
const data = expressionsSource(configArr[i], { ...json, data: resp });
|
const data = expressionsSource(configArr[i], { ...json, data: resp });
|
||||||
const id = configArr[i].id;
|
const id = configArr[i].id;
|
||||||
@@ -205,7 +270,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const partArr = chunkArray(configList, DashboardMaxQueryWidgets);
|
const partArr = chunkArray(configList, DashboardMaxQueryWidgets);
|
||||||
const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
|
const promiseArr = partArr.map((d: DashboardWidgetConfig[]) => fetchMetrics(d));
|
||||||
const responseList = await Promise.all(promiseArr);
|
const responseList = await Promise.all(promiseArr);
|
||||||
let resp = {};
|
let resp = {};
|
||||||
for (const item of responseList) {
|
for (const item of responseList) {
|
||||||
@@ -218,7 +283,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function useExpressionsQueryPodsMetrics(
|
export async function useExpressionsQueryPodsMetrics(
|
||||||
allPods: Array<(Instance | Endpoint | Service) & Indexable>,
|
allPods: Array<PodWithMetrics>,
|
||||||
config: {
|
config: {
|
||||||
expressions: string[];
|
expressions: string[];
|
||||||
subExpressions: string[];
|
subExpressions: string[];
|
||||||
@@ -226,7 +291,7 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
},
|
},
|
||||||
scope: string,
|
scope: string,
|
||||||
) {
|
) {
|
||||||
function expressionsGraphqlPods(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
function expressionsGraphqlPods(pods: Array<PodWithMetrics>) {
|
||||||
const metrics: string[] = [];
|
const metrics: string[] = [];
|
||||||
const subMetrics: string[] = [];
|
const subMetrics: string[] = [];
|
||||||
config.expressions = config.expressions || [];
|
config.expressions = config.expressions || [];
|
||||||
@@ -248,7 +313,7 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
};
|
};
|
||||||
const variables: string[] = [`$duration: Duration!`];
|
const variables: string[] = [`$duration: Duration!`];
|
||||||
const currentService = selectorStore.currentService || ({} as Service);
|
const currentService = selectorStore.currentService || ({} as Service);
|
||||||
const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => {
|
const fragmentList = pods.map((d: PodWithMetrics, index: number) => {
|
||||||
const entity = {
|
const entity = {
|
||||||
serviceName: scope === "Service" ? d.label : currentService.label,
|
serviceName: scope === "Service" ? d.label : currentService.label,
|
||||||
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
|
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
|
||||||
@@ -285,12 +350,20 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function expressionsPodsSource(
|
function expressionsPodsSource(
|
||||||
resp: { errors: string; data: Indexable },
|
resp: { errors: string; data: Record<string, ExecExpressionResponse> },
|
||||||
pods: Array<(Instance | Endpoint | Service) & Indexable>,
|
pods: PodWithMetrics[],
|
||||||
): Indexable {
|
): ExpressionsPodsSourceResult {
|
||||||
if (resp.errors) {
|
if (resp.errors) {
|
||||||
ElMessage.error(resp.errors);
|
ElMessage.error(resp.errors);
|
||||||
return {};
|
return {
|
||||||
|
data: [],
|
||||||
|
names: [],
|
||||||
|
subNames: [],
|
||||||
|
metricConfigArr: [],
|
||||||
|
metricTypesArr: [],
|
||||||
|
expressionsTips: [],
|
||||||
|
subExpressionsTips: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const names: string[] = [];
|
const names: string[] = [];
|
||||||
const subNames: string[] = [];
|
const subNames: string[] = [];
|
||||||
@@ -298,37 +371,37 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
const metricTypesArr: string[] = [];
|
const metricTypesArr: string[] = [];
|
||||||
const expressionsTips: string[] = [];
|
const expressionsTips: string[] = [];
|
||||||
const subExpressionsTips: string[] = [];
|
const subExpressionsTips: string[] = [];
|
||||||
const data = pods.map((d: any, idx: number) => {
|
const data = pods.map((d: PodWithMetrics, idx: number) => {
|
||||||
for (let index = 0; index < config.expressions.length; index++) {
|
for (let index = 0; index < config.expressions.length; index++) {
|
||||||
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[index]) || {};
|
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[index]) || {};
|
||||||
const k = "expression" + idx + index;
|
const k = "expression" + idx + index;
|
||||||
const sub = "subexpression" + idx + index;
|
const sub = "subexpression" + idx + index;
|
||||||
const obj = resp.data[k] || {};
|
const obj = resp.data[k] || {};
|
||||||
const results = obj.results || [];
|
const results = obj.results || [];
|
||||||
const typesOfMQE = obj.type || "";
|
const typesOfMQE = String(obj.type ?? "");
|
||||||
const subObj = resp.data[sub] || {};
|
const subObj = resp.data[sub] || {};
|
||||||
const subResults = subObj.results || [];
|
const subResults = subObj.results || [];
|
||||||
|
|
||||||
expressionsTips.push(obj.error);
|
expressionsTips.push(obj.error || "");
|
||||||
subExpressionsTips.push(subObj.error);
|
subExpressionsTips.push(subObj.error || "");
|
||||||
if (results.length > 1) {
|
if (results.length > 1) {
|
||||||
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
||||||
const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
let name = results[i].metric.labels[0].value || "";
|
let name: string = results[i].metric?.labels?.[0]?.value ?? "";
|
||||||
const subValues = subResults[i] && subResults[i].values.map((d: { value: unknown }) => d.value);
|
const subValues = subResults[i] && subResults[i].values.map((d: { value: unknown }) => d.value);
|
||||||
const num = labelsIdx.findIndex((d: string) => d === results[i].metric.labels[0].value);
|
const num = labelsIdx.findIndex((d: string) => d === (results[i].metric?.labels?.[0]?.value ?? ""));
|
||||||
|
|
||||||
if (labels[num]) {
|
if (labels[num]) {
|
||||||
name = labels[num];
|
name = labels[num];
|
||||||
}
|
}
|
||||||
if (!d[name]) {
|
if (!d[name]) {
|
||||||
d[name] = {};
|
d[name] = {} as MetricEntry;
|
||||||
}
|
}
|
||||||
if (subValues) {
|
if (subValues) {
|
||||||
d[name]["values"] = subValues;
|
d[name]["values"] = subValues;
|
||||||
}
|
}
|
||||||
d[name]["avg"] = (results[i].values[0] || {}).value;
|
d[name]["avg"] = (results[i].values?.[0] || {}).value;
|
||||||
|
|
||||||
const j = names.find((d: string) => d === name);
|
const j = names.find((d: string) => d === name);
|
||||||
|
|
||||||
@@ -342,17 +415,17 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
if (!results[0]) {
|
if (!results[0]) {
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
const name = config.expressions[index] || "";
|
const name: string = config.expressions[index] || "";
|
||||||
const subName = config.subExpressions[index] || "";
|
const subName: string = config.subExpressions[index] || "";
|
||||||
if (!d[name]) {
|
if (!d[name]) {
|
||||||
d[name] = {};
|
d[name] = {} as MetricEntry;
|
||||||
}
|
}
|
||||||
d[name]["avg"] = [(results[0].values[0] || {}).value];
|
d[name]["avg"] = [(results[0].values?.[0] || {}).value];
|
||||||
if (subResults[0]) {
|
if (subResults[0]) {
|
||||||
if (!d[subName]) {
|
if (!d[subName]) {
|
||||||
d[subName] = {};
|
d[subName] = {} as MetricEntry;
|
||||||
}
|
}
|
||||||
d[subName]["values"] = subResults[0].values.map((d: { value: number }) => d.value);
|
d[subName]["values"] = subResults[0].values.map((d: { value: unknown }) => d.value as number);
|
||||||
}
|
}
|
||||||
const j = names.find((d: string) => d === name);
|
const j = names.find((d: string) => d === name);
|
||||||
if (!j) {
|
if (!j) {
|
||||||
@@ -369,7 +442,7 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
|
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPodsExpressionValues(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
async function fetchPodsExpressionValues(pods: Array<PodWithMetrics>): Promise<ExpressionsPodsSourceResult> {
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const params = await expressionsGraphqlPods(pods);
|
const params = await expressionsGraphqlPods(pods);
|
||||||
|
|
||||||
@@ -379,9 +452,20 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
|
|
||||||
if (json.errors) {
|
if (json.errors) {
|
||||||
ElMessage.error(json.errors);
|
ElMessage.error(json.errors);
|
||||||
return {};
|
return {
|
||||||
|
data: [],
|
||||||
|
names: [],
|
||||||
|
subNames: [],
|
||||||
|
metricConfigArr: [],
|
||||||
|
metricTypesArr: [],
|
||||||
|
expressionsTips: [],
|
||||||
|
subExpressionsTips: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const expressionParams = expressionsPodsSource(json, pods);
|
const expressionParams = expressionsPodsSource(
|
||||||
|
json as { errors: string; data: Record<string, ExecExpressionResponse> },
|
||||||
|
pods,
|
||||||
|
);
|
||||||
|
|
||||||
return expressionParams;
|
return expressionParams;
|
||||||
}
|
}
|
||||||
@@ -390,11 +474,19 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
for (let i = 0; i < allPods.length; i += MaximumEntities) {
|
for (let i = 0; i < allPods.length; i += MaximumEntities) {
|
||||||
result.push(allPods.slice(i, i + MaximumEntities));
|
result.push(allPods.slice(i, i + MaximumEntities));
|
||||||
}
|
}
|
||||||
const promiseArr = result.map((d: Array<(Instance | Endpoint | Service) & Indexable>) =>
|
const promiseArr: Array<Promise<ExpressionsPodsSourceResult>> = result.map((d: Array<PodWithMetrics>) =>
|
||||||
fetchPodsExpressionValues(d),
|
fetchPodsExpressionValues(d),
|
||||||
);
|
);
|
||||||
const responseList = await Promise.all(promiseArr);
|
const responseList: ExpressionsPodsSourceResult[] = await Promise.all(promiseArr);
|
||||||
let resp: Indexable = { data: [], expressionsTips: [], subExpressionsTips: [] };
|
let resp: ExpressionsPodsSourceResult = {
|
||||||
|
data: [],
|
||||||
|
expressionsTips: [],
|
||||||
|
subExpressionsTips: [],
|
||||||
|
names: [],
|
||||||
|
subNames: [],
|
||||||
|
metricConfigArr: [],
|
||||||
|
metricTypesArr: [],
|
||||||
|
};
|
||||||
for (const item of responseList) {
|
for (const item of responseList) {
|
||||||
resp = {
|
resp = {
|
||||||
...item,
|
...item,
|
||||||
@@ -416,7 +508,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
|||||||
duration: appStore.durationTime,
|
duration: appStore.durationTime,
|
||||||
};
|
};
|
||||||
const variables: string[] = [`$duration: Duration!`];
|
const variables: string[] = [`$duration: Duration!`];
|
||||||
const fragmentList = entities.map((d: Indexable, index: number) => {
|
const fragmentList = entities.map((d: Call | Node, index: number) => {
|
||||||
let serviceName;
|
let serviceName;
|
||||||
let destServiceName;
|
let destServiceName;
|
||||||
let endpointName;
|
let endpointName;
|
||||||
@@ -425,7 +517,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
|||||||
let destEndpointName;
|
let destEndpointName;
|
||||||
let normal = false;
|
let normal = false;
|
||||||
let destNormal;
|
let destNormal;
|
||||||
if (d.sourceObj && d.targetObj) {
|
if ("sourceObj" in d && "targetObj" in d && d.sourceObj && d.targetObj) {
|
||||||
// instances = Calls
|
// instances = Calls
|
||||||
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
|
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
|
||||||
destServiceName = d.targetObj.serviceName || d.targetObj.name;
|
destServiceName = d.targetObj.serviceName || d.targetObj.name;
|
||||||
@@ -441,16 +533,17 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// instances = Nodes
|
// instances = Nodes
|
||||||
serviceName = d.serviceName || d.name;
|
const node = d as Node;
|
||||||
normal = d.normal || d.isReal || false;
|
serviceName = node.serviceName || node.name;
|
||||||
|
normal = Boolean((node as unknown as { normal?: boolean }).normal) || node.isReal || false;
|
||||||
if (EntityType[3].value === dashboardStore.entity) {
|
if (EntityType[3].value === dashboardStore.entity) {
|
||||||
serviceInstanceName = d.name;
|
serviceInstanceName = node.name;
|
||||||
}
|
}
|
||||||
if (EntityType[4].value === dashboardStore.entity) {
|
if (EntityType[4].value === dashboardStore.entity) {
|
||||||
serviceInstanceName = d.name;
|
serviceInstanceName = node.name;
|
||||||
}
|
}
|
||||||
if (EntityType[2].value === dashboardStore.entity) {
|
if (EntityType[2].value === dashboardStore.entity) {
|
||||||
endpointName = d.name;
|
endpointName = node.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const entity = {
|
const entity = {
|
||||||
@@ -479,8 +572,8 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
|||||||
|
|
||||||
return { queryStr, conditions };
|
return { queryStr, conditions };
|
||||||
}
|
}
|
||||||
function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
|
function handleExpressionValues(partMetrics: string[], resp: Record<string, ExecExpressionResponse>) {
|
||||||
const obj: Indexable = {};
|
const obj: Record<string, { values: Array<{ value: unknown; id: string }> }> = {};
|
||||||
for (let idx = 0; idx < instances.length; idx++) {
|
for (let idx = 0; idx < instances.length; idx++) {
|
||||||
for (let index = 0; index < partMetrics.length; index++) {
|
for (let index = 0; index < partMetrics.length; index++) {
|
||||||
const k = "expression" + idx + index;
|
const k = "expression" + idx + index;
|
||||||
@@ -491,7 +584,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
obj[partMetrics[index]].values.push({
|
obj[partMetrics[index]].values.push({
|
||||||
value: resp[k] && resp[k].results[0] && resp[k].results[0].values[0].value,
|
value: resp[k]?.results?.[0]?.values?.[0]?.value,
|
||||||
id: instances[idx].id,
|
id: instances[idx].id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||||
import type { Trace, Span } from "@/types/trace";
|
import type { Trace, Span, TraceCondition } from "@/types/trace";
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import graphql from "@/graphql";
|
import graphql from "@/graphql";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
@@ -32,10 +32,10 @@ interface TraceState {
|
|||||||
traceList: Trace[];
|
traceList: Trace[];
|
||||||
traceSpans: Span[];
|
traceSpans: Span[];
|
||||||
currentTrace: Nullable<Trace>;
|
currentTrace: Nullable<Trace>;
|
||||||
conditions: Recordable;
|
conditions: TraceCondition;
|
||||||
traceSpanLogs: LogItem[];
|
traceSpanLogs: LogItem[];
|
||||||
selectorStore: Recordable;
|
selectorStore: ReturnType<typeof useSelectorStore>;
|
||||||
selectedSpan: Recordable<Span>;
|
selectedSpan: Nullable<Span>;
|
||||||
serviceList: string[];
|
serviceList: string[];
|
||||||
}
|
}
|
||||||
const { getDurationTime } = useDuration();
|
const { getDurationTime } = useDuration();
|
||||||
@@ -49,7 +49,7 @@ export const traceStore = defineStore({
|
|||||||
traceList: [],
|
traceList: [],
|
||||||
traceSpans: [],
|
traceSpans: [],
|
||||||
currentTrace: null,
|
currentTrace: null,
|
||||||
selectedSpan: {},
|
selectedSpan: null,
|
||||||
conditions: {
|
conditions: {
|
||||||
queryDuration: getDurationTime(),
|
queryDuration: getDurationTime(),
|
||||||
traceState: "ALL",
|
traceState: "ALL",
|
||||||
|
@@ -65,6 +65,10 @@ export interface LayoutConfig {
|
|||||||
nodeMetricConfig?: MetricConfigOpt[];
|
nodeMetricConfig?: MetricConfigOpt[];
|
||||||
instanceDashboardName?: string;
|
instanceDashboardName?: string;
|
||||||
processDashboardName?: string;
|
processDashboardName?: string;
|
||||||
|
autoPeriod?: number;
|
||||||
|
auto?: number;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LegendMQE = {
|
type LegendMQE = {
|
||||||
|
@@ -21,8 +21,26 @@ export interface Call {
|
|||||||
id: string;
|
id: string;
|
||||||
detectPoints: string[];
|
detectPoints: string[];
|
||||||
type?: string;
|
type?: string;
|
||||||
sourceObj?: any;
|
sourceObj?: {
|
||||||
targetObj?: any;
|
serviceName?: string;
|
||||||
|
name?: string;
|
||||||
|
normal?: boolean;
|
||||||
|
isReal?: boolean;
|
||||||
|
id?: string;
|
||||||
|
layers?: string[];
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
};
|
||||||
|
targetObj?: {
|
||||||
|
serviceName?: string;
|
||||||
|
name?: string;
|
||||||
|
normal?: boolean;
|
||||||
|
isReal?: boolean;
|
||||||
|
id?: string;
|
||||||
|
layers?: string[];
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
};
|
||||||
value?: number;
|
value?: number;
|
||||||
lowerArc?: boolean;
|
lowerArc?: boolean;
|
||||||
sourceComponents: string[];
|
sourceComponents: string[];
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { DurationTime } from "./app";
|
||||||
export interface Trace {
|
export interface Trace {
|
||||||
duration: number;
|
duration: number;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
@@ -107,3 +109,10 @@ export interface SpanAttachedEvent {
|
|||||||
tags: KeyValue[];
|
tags: KeyValue[];
|
||||||
summary: KeyValue[];
|
summary: KeyValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TraceCondition {
|
||||||
|
queryDuration: DurationTime;
|
||||||
|
traceState: string;
|
||||||
|
queryOrder: string;
|
||||||
|
paging: { pageNum: number; pageSize: number };
|
||||||
|
}
|
||||||
|
@@ -24,7 +24,7 @@ limitations under the License. -->
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="widget-chart" :style="{ height: config.height - 60 + 'px' }">
|
<div class="widget-chart" :style="{ height: (config.height || 0) - 60 + 'px' }">
|
||||||
<component
|
<component
|
||||||
:is="graph.type"
|
:is="graph.type"
|
||||||
:intervalTime="appStoreWithOut.intervalTime"
|
:intervalTime="appStoreWithOut.intervalTime"
|
||||||
@@ -55,10 +55,12 @@ limitations under the License. -->
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
import { useDashboardQueryProcessor, DashboardWidgetConfig } from "@/hooks/useExpressionsProcessor";
|
||||||
import graphs from "./graphs";
|
import graphs from "./graphs";
|
||||||
import { EntityType } from "./data";
|
import { EntityType } from "./data";
|
||||||
import timeFormat from "@/utils/timeFormat";
|
import timeFormat from "@/utils/timeFormat";
|
||||||
|
import { LayoutConfig } from "@/types/dashboard";
|
||||||
|
import { ExpressionsSourceResult } from "@/hooks/useExpressionsProcessor";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "WidgetPage",
|
name: "WidgetPage",
|
||||||
@@ -70,9 +72,11 @@ limitations under the License. -->
|
|||||||
const appStoreWithOut = useAppStoreWithOut();
|
const appStoreWithOut = useAppStoreWithOut();
|
||||||
const selectorStore = useSelectorStore();
|
const selectorStore = useSelectorStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const config = computed<any>(() => JSON.parse(decodeURIComponent(route.params.config as string) as string));
|
const config = computed<LayoutConfig>(() =>
|
||||||
|
JSON.parse(decodeURIComponent(route.params.config as string) as string),
|
||||||
|
);
|
||||||
const graph = computed(() => config.value.graph || {});
|
const graph = computed(() => config.value.graph || {});
|
||||||
const source = ref<unknown>({});
|
const source = ref<ExpressionsSourceResult | {}>({});
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const title = computed(() => (config.value.widget && config.value.widget.title) || "");
|
const title = computed(() => (config.value.widget && config.value.widget.title) || "");
|
||||||
@@ -86,7 +90,7 @@ limitations under the License. -->
|
|||||||
const { auto, autoPeriod } = config.value;
|
const { auto, autoPeriod } = config.value;
|
||||||
if (auto) {
|
if (auto) {
|
||||||
await setDuration();
|
await setDuration();
|
||||||
appStoreWithOut.setReloadTimer(setInterval(setDuration, autoPeriod * 1000));
|
appStoreWithOut.setReloadTimer(setInterval(setDuration, (autoPeriod ?? 0) * 1000));
|
||||||
} else {
|
} else {
|
||||||
const duration = JSON.parse(route.params.duration as string);
|
const duration = JSON.parse(route.params.duration as string);
|
||||||
appStoreWithOut.setDuration(duration);
|
appStoreWithOut.setDuration(duration);
|
||||||
@@ -95,7 +99,7 @@ limitations under the License. -->
|
|||||||
await queryMetrics();
|
await queryMetrics();
|
||||||
}
|
}
|
||||||
async function setDuration() {
|
async function setDuration() {
|
||||||
const dates: Date[] = [new Date(new Date().getTime() - config.value.auto), new Date()];
|
const dates: Date[] = [new Date(new Date().getTime() - (config.value.auto ?? 0)), new Date()];
|
||||||
|
|
||||||
appStoreWithOut.setDuration(timeFormat(dates));
|
appStoreWithOut.setDuration(timeFormat(dates));
|
||||||
}
|
}
|
||||||
@@ -130,17 +134,17 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
async function queryMetrics() {
|
async function queryMetrics() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
const metrics = await useDashboardQueryProcessor([
|
||||||
await useDashboardQueryProcessor([
|
|
||||||
{
|
{
|
||||||
metrics: config.value.expressions || [],
|
metrics: config.value.expressions || [],
|
||||||
metricConfig: config.value.metricConfig || [],
|
metricConfig: config.value.metricConfig || [],
|
||||||
subExpressions: config.value.subExpressions || [],
|
subExpressions: (config.value.subExpressions || []) as string[],
|
||||||
id: config.value.i,
|
id: config.value.i,
|
||||||
},
|
},
|
||||||
]);
|
] as DashboardWidgetConfig[]);
|
||||||
const params = metrics[config.value.i];
|
const params: ExpressionsSourceResult = (metrics as Record<string, ExpressionsSourceResult>)[
|
||||||
loading.value = false;
|
config.value.i as string
|
||||||
|
];
|
||||||
source.value = params.source || {};
|
source.value = params.source || {};
|
||||||
typesOfMQE.value = params.typesOfMQE;
|
typesOfMQE.value = params.typesOfMQE;
|
||||||
}
|
}
|
||||||
|
@@ -165,7 +165,7 @@ limitations under the License. -->
|
|||||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||||
EntityType[2].value,
|
EntityType[2].value,
|
||||||
);
|
);
|
||||||
currentEndpoints.value = params.data;
|
currentEndpoints.value = params.data as Endpoint[];
|
||||||
colMetrics.value = params.names;
|
colMetrics.value = params.names;
|
||||||
colSubMetrics.value = params.subNames;
|
colSubMetrics.value = params.subNames;
|
||||||
metricConfig.value = params.metricConfigArr;
|
metricConfig.value = params.metricConfigArr;
|
||||||
|
@@ -91,6 +91,7 @@ limitations under the License. -->
|
|||||||
import getDashboard from "@/hooks/useDashboardsSession";
|
import getDashboard from "@/hooks/useDashboardsSession";
|
||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||||
import ColumnGraph from "./components/ColumnGraph.vue";
|
import ColumnGraph from "./components/ColumnGraph.vue";
|
||||||
|
import type { PodWithMetrics } from "@/hooks/useExpressionsProcessor";
|
||||||
|
|
||||||
/*global defineProps */
|
/*global defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -176,11 +177,11 @@ limitations under the License. -->
|
|||||||
|
|
||||||
if (expressions.length && expressions[0]) {
|
if (expressions.length && expressions[0]) {
|
||||||
const params = await useExpressionsQueryPodsMetrics(
|
const params = await useExpressionsQueryPodsMetrics(
|
||||||
currentInstances,
|
currentInstances as PodWithMetrics[],
|
||||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||||
EntityType[3].value,
|
EntityType[3].value,
|
||||||
);
|
);
|
||||||
instances.value = params.data;
|
instances.value = params.data as Instance[];
|
||||||
colMetrics.value = params.names;
|
colMetrics.value = params.names;
|
||||||
colSubMetrics.value = params.subNames;
|
colSubMetrics.value = params.subNames;
|
||||||
typesOfMQE.value = params.metricTypesArr;
|
typesOfMQE.value = params.metricTypesArr;
|
||||||
|
@@ -78,14 +78,14 @@ limitations under the License. -->
|
|||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import type { Service } from "@/types/selector";
|
import type { Service } from "@/types/selector";
|
||||||
import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor";
|
import { useExpressionsQueryPodsMetrics, PodWithMetrics } from "@/hooks/useExpressionsProcessor";
|
||||||
import { EntityType } from "../data";
|
import { EntityType } from "../data";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import getDashboard from "@/hooks/useDashboardsSession";
|
import getDashboard from "@/hooks/useDashboardsSession";
|
||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||||
import ColumnGraph from "./components/ColumnGraph.vue";
|
import ColumnGraph from "./components/ColumnGraph.vue";
|
||||||
|
|
||||||
interface ServiceWithGroup extends Service {
|
export interface ServiceWithGroup extends Service {
|
||||||
merge: boolean;
|
merge: boolean;
|
||||||
group: string;
|
group: string;
|
||||||
}
|
}
|
||||||
@@ -219,11 +219,11 @@ limitations under the License. -->
|
|||||||
|
|
||||||
if (expressions.length && expressions[0]) {
|
if (expressions.length && expressions[0]) {
|
||||||
const params = await useExpressionsQueryPodsMetrics(
|
const params = await useExpressionsQueryPodsMetrics(
|
||||||
currentServices,
|
currentServices as PodWithMetrics[],
|
||||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||||
EntityType[0].value,
|
EntityType[0].value,
|
||||||
);
|
);
|
||||||
services.value = params.data;
|
services.value = params.data as ServiceWithGroup[];
|
||||||
colMetrics.value = params.names;
|
colMetrics.value = params.names;
|
||||||
colSubMetrics.value = params.subNames;
|
colSubMetrics.value = params.subNames;
|
||||||
metricConfig.value = params.metricConfigArr;
|
metricConfig.value = params.metricConfigArr;
|
||||||
|
@@ -19,15 +19,15 @@ limitations under the License. -->
|
|||||||
v-for="(item, index) in columns"
|
v-for="(item, index) in columns"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="item.label"
|
:class="item.label"
|
||||||
@click="selectLog(item.label, data[item.label])"
|
@click="selectLog(item.label, getDataValue(item.label))"
|
||||||
>
|
>
|
||||||
<span v-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
|
<span v-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
|
||||||
<span class="blue" v-else-if="item.label === 'traceId'">
|
<span class="blue" v-else-if="item.label === 'traceId'">
|
||||||
<el-tooltip content="Trace Link" v-if="!noLink && data[item.label]">
|
<el-tooltip content="Trace Link" v-if="!noLink && getDataValue(item.label)">
|
||||||
<Icon iconName="merge" />
|
<Icon iconName="merge" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span v-else v-html="highlightKeywords(data[item.label])"></span>
|
<span v-else v-html="highlightKeywords(getDataValue(item.label))"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -40,10 +40,11 @@ limitations under the License. -->
|
|||||||
import type { LayoutConfig, DashboardItem } from "@/types/dashboard";
|
import type { LayoutConfig, DashboardItem } from "@/types/dashboard";
|
||||||
import { WidgetType } from "@/views/dashboard/data";
|
import { WidgetType } from "@/views/dashboard/data";
|
||||||
import { useLogStore } from "@/store/modules/log";
|
import { useLogStore } from "@/store/modules/log";
|
||||||
|
import type { LogItem } from "@/types/log";
|
||||||
|
|
||||||
/*global defineProps, defineEmits */
|
/*global defineProps, defineEmits */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: { type: Object as any, default: () => ({}) },
|
data: { type: Object as PropType<LogItem>, default: () => ({}) },
|
||||||
noLink: { type: Boolean, default: true },
|
noLink: { type: Boolean, default: true },
|
||||||
config: { type: Object as PropType<LayoutConfig>, default: () => ({}) },
|
config: { type: Object as PropType<LayoutConfig>, default: () => ({}) },
|
||||||
});
|
});
|
||||||
@@ -64,6 +65,10 @@ limitations under the License. -->
|
|||||||
return `${content}`.replace(regex, (match) => `<span style="color: red">${match}</span>`);
|
return `${content}`.replace(regex, (match) => `<span style="color: red">${match}</span>`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getDataValue(label: string) {
|
||||||
|
return props.data[label as keyof LogItem] as string;
|
||||||
|
}
|
||||||
|
|
||||||
function selectLog(label: string, value: string) {
|
function selectLog(label: string, value: string) {
|
||||||
if (label === "traceId") {
|
if (label === "traceId") {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
@@ -62,20 +62,25 @@ export function layout(levels: Node[][], calls: Call[], radius: number) {
|
|||||||
|
|
||||||
export function computeCallPos(calls: Call[], radius: number) {
|
export function computeCallPos(calls: Call[], radius: number) {
|
||||||
for (const [index, call] of calls.entries()) {
|
for (const [index, call] of calls.entries()) {
|
||||||
const centrePoints = [call.sourceObj.x, call.sourceObj.y, call.targetObj.x, call.targetObj.y];
|
const centrePoints = [
|
||||||
|
call.sourceObj?.x || 0,
|
||||||
|
call.sourceObj?.y || 0,
|
||||||
|
call.targetObj?.x || 0,
|
||||||
|
call.targetObj?.y || 0,
|
||||||
|
];
|
||||||
for (const [idx, link] of calls.entries()) {
|
for (const [idx, link] of calls.entries()) {
|
||||||
if (
|
if (
|
||||||
index < idx &&
|
index < idx &&
|
||||||
call.id !== link.id &&
|
call.id !== link.id &&
|
||||||
call.sourceObj.x === link.targetObj.x &&
|
call.sourceObj?.x === link.targetObj?.x &&
|
||||||
call.sourceObj.y === link.targetObj.y &&
|
call.sourceObj?.y === link.targetObj?.y &&
|
||||||
call.targetObj.x === link.sourceObj.x &&
|
call.targetObj?.x === link.sourceObj?.x &&
|
||||||
call.targetObj.y === link.sourceObj.y
|
call.targetObj?.y === link.sourceObj?.y
|
||||||
) {
|
) {
|
||||||
if (call.targetObj.y === call.sourceObj.y) {
|
if (call.targetObj?.y === call.sourceObj?.y) {
|
||||||
centrePoints[1] = centrePoints[1] - 8;
|
centrePoints[1] = centrePoints[1] - 8;
|
||||||
centrePoints[3] = centrePoints[3] - 8;
|
centrePoints[3] = centrePoints[3] - 8;
|
||||||
} else if (call.targetObj.x === call.sourceObj.x) {
|
} else if (call.targetObj?.x === call.sourceObj?.x) {
|
||||||
centrePoints[0] = centrePoints[0] - 8;
|
centrePoints[0] = centrePoints[0] - 8;
|
||||||
centrePoints[2] = centrePoints[2] - 8;
|
centrePoints[2] = centrePoints[2] - 8;
|
||||||
} else {
|
} else {
|
||||||
@@ -127,14 +132,14 @@ function findMostFrequent(arr: Call[]) {
|
|||||||
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
const item = arr[i];
|
const item = arr[i];
|
||||||
count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1;
|
count[item.sourceObj?.id || ""] = (count[item.sourceObj?.id || ""] || 0) + 1;
|
||||||
if (count[item.sourceObj.id] > maxCount) {
|
if (count[item.sourceObj?.id || ""] > maxCount) {
|
||||||
maxCount = count[item.sourceObj.id];
|
maxCount = count[item.sourceObj?.id || ""];
|
||||||
maxItem = item.sourceObj;
|
maxItem = item.sourceObj;
|
||||||
}
|
}
|
||||||
count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1;
|
count[item.targetObj?.id || ""] = (count[item.targetObj?.id || ""] || 0) + 1;
|
||||||
if (count[item.targetObj.id] > maxCount) {
|
if (count[item.targetObj?.id || ""] > maxCount) {
|
||||||
maxCount = count[item.targetObj.id];
|
maxCount = count[item.targetObj?.id || ""];
|
||||||
maxItem = item.targetObj;
|
maxItem = item.targetObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +161,7 @@ export function computeLevels(calls: Call[], nodeList: Node[], arr: Node[][]) {
|
|||||||
const index = nodes.findIndex((n: Node) => n.type === "USER");
|
const index = nodes.findIndex((n: Node) => n.type === "USER");
|
||||||
let key = index;
|
let key = index;
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
key = nodes.findIndex((n: Node) => n.id === node.id);
|
key = nodes.findIndex((n: Node) => n.id === node?.id);
|
||||||
}
|
}
|
||||||
levels.push([nodes[key]]);
|
levels.push([nodes[key]]);
|
||||||
nodes = nodes.filter((_: unknown, index: number) => index !== key);
|
nodes = nodes.filter((_: unknown, index: number) => index !== key);
|
||||||
|
@@ -109,7 +109,7 @@ limitations under the License. -->
|
|||||||
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric?.value} ${opt.unit || ""}</div>`;
|
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric?.value} ${opt.unit || ""}</div>`;
|
||||||
});
|
});
|
||||||
const html = [
|
const html = [
|
||||||
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
|
`<div>${data.sourceObj?.serviceName} -> ${data.targetObj?.serviceName}</div>`,
|
||||||
...htmlServer,
|
...htmlServer,
|
||||||
...htmlClient,
|
...htmlClient,
|
||||||
].join(" ");
|
].join(" ");
|
||||||
|
@@ -386,7 +386,10 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
function handleLinkClick(event: MouseEvent, d: Call) {
|
function handleLinkClick(event: MouseEvent, d: Call) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (!d.sourceObj.layers.includes(dashboardStore.layerId) || !d.targetObj.layers.includes(dashboardStore.layerId)) {
|
if (
|
||||||
|
!d.sourceObj?.layers?.includes(dashboardStore.layerId) ||
|
||||||
|
!d.targetObj?.layers?.includes(dashboardStore.layerId)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
topologyStore.setNode(null);
|
topologyStore.setNode(null);
|
||||||
@@ -406,7 +409,10 @@ limitations under the License. -->
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dashboardStore.setEntity(dashboard.entity);
|
dashboardStore.setEntity(dashboard.entity);
|
||||||
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj.id}/${d.targetObj.id}/${dashboard.name}`;
|
if (!d.sourceObj || !d.targetObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj?.id}/${d.targetObj?.id}/${dashboard.name}`;
|
||||||
const routeUrl = router.resolve({ path });
|
const routeUrl = router.resolve({ path });
|
||||||
window.open(routeUrl.href, "_blank");
|
window.open(routeUrl.href, "_blank");
|
||||||
dashboardStore.setEntity(origin);
|
dashboardStore.setEntity(origin);
|
||||||
|
@@ -49,12 +49,12 @@ limitations under the License. -->
|
|||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
[FiltersKeys.minTraceDuration, FiltersKeys.maxTraceDuration].includes(key) &&
|
[FiltersKeys.minTraceDuration, FiltersKeys.maxTraceDuration].includes(key) &&
|
||||||
!isNaN(traceStore.conditions[FiltersKeys[key]])
|
!isNaN(getConditionValue(key) as number)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
|
{{ t(key) }}: {{ getConditionValue(key) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="key !== 'duration'"> {{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }} </span>
|
<span v-else-if="key !== 'duration'"> {{ t(key) }}: {{ getConditionValue(key) }} </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
@@ -101,6 +101,16 @@ limitations under the License. -->
|
|||||||
minTraceDuration: "minTraceDuration",
|
minTraceDuration: "minTraceDuration",
|
||||||
maxTraceDuration: "maxTraceDuration",
|
maxTraceDuration: "maxTraceDuration",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Type-safe function to get condition value
|
||||||
|
const getConditionValue = (key: string): string | number | undefined => {
|
||||||
|
const conditionKey = FiltersKeys[key];
|
||||||
|
if (!conditionKey) return undefined;
|
||||||
|
|
||||||
|
// Type assertion for dynamic properties that are added at runtime
|
||||||
|
return (traceStore.conditions as Recordable)[conditionKey];
|
||||||
|
};
|
||||||
|
|
||||||
/*global defineProps, Recordable */
|
/*global defineProps, Recordable */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
needQuery: { type: Boolean, default: true },
|
needQuery: { type: Boolean, default: true },
|
||||||
|
Reference in New Issue
Block a user