1// Copyright 2021 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14import chai from 'chai'; 15import { analyzeCompletion, computeStartCompletePosition, ContextKind } from './hybrid'; 16import { createEditorState, mockedMetricsTerms, mockPrometheusServer } from '../../test/utils'; 17import { Completion, CompletionContext } from '@codemirror/autocomplete'; 18import { 19 aggregateOpModifierTerms, 20 aggregateOpTerms, 21 atModifierTerms, 22 binOpModifierTerms, 23 binOpTerms, 24 durationTerms, 25 functionIdentifierTerms, 26 matchOpTerms, 27 numberTerms, 28 snippets, 29} from './promql.terms'; 30import { EqlSingle, Neq } from '../grammar/parser.terms'; 31import { syntaxTree } from '@codemirror/language'; 32import { newCompleteStrategy } from './index'; 33 34describe('analyzeCompletion test', () => { 35 const testCases = [ 36 { 37 title: 'empty expr', 38 expr: '', 39 pos: 0, 40 expectedContext: [ 41 { 42 kind: ContextKind.MetricName, 43 metricName: '', 44 }, 45 { kind: ContextKind.Function }, 46 { kind: ContextKind.Aggregation }, 47 { kind: ContextKind.Number }, 48 ], 49 }, 50 { 51 title: 'simple metric & number autocompletion', 52 expr: 'go_', 53 pos: 3, // cursor is at the end of the expr 54 expectedContext: [ 55 { 56 kind: ContextKind.MetricName, 57 metricName: 'go_', 58 }, 59 { kind: ContextKind.Function }, 60 { kind: ContextKind.Aggregation }, 61 { kind: ContextKind.Number }, 62 ], 63 }, 64 { 65 title: 'metric/function/aggregation autocompletion', 66 expr: 'sum()', 67 pos: 4, 68 expectedContext: [ 69 { 70 kind: ContextKind.MetricName, 71 metricName: '', 72 }, 73 { kind: ContextKind.Function }, 74 { kind: ContextKind.Aggregation }, 75 ], 76 }, 77 { 78 title: 'metric/function/aggregation autocompletion 2', 79 expr: 'sum(rat)', 80 pos: 7, 81 expectedContext: [ 82 { 83 kind: ContextKind.MetricName, 84 metricName: 'rat', 85 }, 86 { kind: ContextKind.Function }, 87 { kind: ContextKind.Aggregation }, 88 ], 89 }, 90 { 91 title: 'metric/function/aggregation autocompletion 3', 92 expr: 'sum(rate())', 93 pos: 9, 94 expectedContext: [ 95 { 96 kind: ContextKind.MetricName, 97 metricName: '', 98 }, 99 { kind: ContextKind.Function }, 100 { kind: ContextKind.Aggregation }, 101 ], 102 }, 103 { 104 title: 'metric/function/aggregation autocompletion 4', 105 expr: 'sum(rate(my_))', 106 pos: 12, 107 expectedContext: [ 108 { 109 kind: ContextKind.MetricName, 110 metricName: 'my_', 111 }, 112 { kind: ContextKind.Function }, 113 { kind: ContextKind.Aggregation }, 114 ], 115 }, 116 { 117 title: 'autocomplete binOp modifier or metric or number', 118 expr: 'metric_name / ignor', 119 pos: 19, 120 expectedContext: [ 121 { kind: ContextKind.MetricName, metricName: 'ignor' }, 122 { kind: ContextKind.Function }, 123 { kind: ContextKind.Aggregation }, 124 { kind: ContextKind.BinOpModifier }, 125 { kind: ContextKind.Number }, 126 ], 127 }, 128 { 129 title: 'autocomplete binOp modifier or metric or number 2', 130 expr: 'sum(http_requests_total{method="GET"} / o)', 131 pos: 41, 132 expectedContext: [ 133 { kind: ContextKind.MetricName, metricName: 'o' }, 134 { kind: ContextKind.Function }, 135 { kind: ContextKind.Aggregation }, 136 { kind: ContextKind.BinOpModifier }, 137 { kind: ContextKind.Number }, 138 ], 139 }, 140 { 141 title: 'autocomplete bool/binOp modifier/metric/number 1', 142 expr: '1 > b)', 143 pos: 5, 144 expectedContext: [ 145 { kind: ContextKind.MetricName, metricName: 'b' }, 146 { kind: ContextKind.Function }, 147 { kind: ContextKind.Aggregation }, 148 { kind: ContextKind.BinOpModifier }, 149 { kind: ContextKind.Number }, 150 { kind: ContextKind.Bool }, 151 ], 152 }, 153 { 154 title: 'autocomplete bool/binOp modifier/metric/number 2', 155 expr: '1 == b)', 156 pos: 6, 157 expectedContext: [ 158 { kind: ContextKind.MetricName, metricName: 'b' }, 159 { kind: ContextKind.Function }, 160 { kind: ContextKind.Aggregation }, 161 { kind: ContextKind.BinOpModifier }, 162 { kind: ContextKind.Number }, 163 { kind: ContextKind.Bool }, 164 ], 165 }, 166 { 167 title: 'autocomplete bool/binOp modifier/metric/number 3', 168 expr: '1 != b)', 169 pos: 6, 170 expectedContext: [ 171 { kind: ContextKind.MetricName, metricName: 'b' }, 172 { kind: ContextKind.Function }, 173 { kind: ContextKind.Aggregation }, 174 { kind: ContextKind.BinOpModifier }, 175 { kind: ContextKind.Number }, 176 { kind: ContextKind.Bool }, 177 ], 178 }, 179 { 180 title: 'autocomplete bool/binOp modifier/metric/number 4', 181 expr: '1 > b)', 182 pos: 5, 183 expectedContext: [ 184 { kind: ContextKind.MetricName, metricName: 'b' }, 185 { kind: ContextKind.Function }, 186 { kind: ContextKind.Aggregation }, 187 { kind: ContextKind.BinOpModifier }, 188 { kind: ContextKind.Number }, 189 { kind: ContextKind.Bool }, 190 ], 191 }, 192 { 193 title: 'autocomplete bool/binOp modifier/metric/number 5', 194 expr: '1 >= b)', 195 pos: 6, 196 expectedContext: [ 197 { kind: ContextKind.MetricName, metricName: 'b' }, 198 { kind: ContextKind.Function }, 199 { kind: ContextKind.Aggregation }, 200 { kind: ContextKind.BinOpModifier }, 201 { kind: ContextKind.Number }, 202 { kind: ContextKind.Bool }, 203 ], 204 }, 205 { 206 title: 'autocomplete bool/binOp modifier/metric/number 6', 207 expr: '1 <= b)', 208 pos: 6, 209 expectedContext: [ 210 { kind: ContextKind.MetricName, metricName: 'b' }, 211 { kind: ContextKind.Function }, 212 { kind: ContextKind.Aggregation }, 213 { kind: ContextKind.BinOpModifier }, 214 { kind: ContextKind.Number }, 215 { kind: ContextKind.Bool }, 216 ], 217 }, 218 { 219 title: 'autocomplete bool/binOp modifier/metric/number 7', 220 expr: '1 < b)', 221 pos: 5, 222 expectedContext: [ 223 { kind: ContextKind.MetricName, metricName: 'b' }, 224 { kind: ContextKind.Function }, 225 { kind: ContextKind.Aggregation }, 226 { kind: ContextKind.BinOpModifier }, 227 { kind: ContextKind.Number }, 228 { kind: ContextKind.Bool }, 229 ], 230 }, 231 { 232 title: 'starting to autocomplete labelName in aggregate modifier', 233 expr: 'sum by ()', 234 pos: 8, // cursor is between the bracket 235 expectedContext: [{ kind: ContextKind.LabelName }], 236 }, 237 { 238 title: 'continue to autocomplete labelName in aggregate modifier', 239 expr: 'sum by (myL)', 240 pos: 11, // cursor is between the bracket after the string myL 241 expectedContext: [{ kind: ContextKind.LabelName }], 242 }, 243 { 244 title: 'autocomplete labelName in a list', 245 expr: 'sum by (myLabel1,)', 246 pos: 17, // cursor is between the bracket after the string myLab 247 expectedContext: [{ kind: ContextKind.LabelName }], 248 }, 249 { 250 title: 'autocomplete labelName in a list 2', 251 expr: 'sum by (myLabel1, myLab)', 252 pos: 23, // cursor is between the bracket after the string myLab 253 expectedContext: [{ kind: ContextKind.LabelName }], 254 }, 255 { 256 title: 'autocomplete labelName associated to a metric', 257 expr: 'metric_name{}', 258 pos: 12, // cursor is between the bracket 259 expectedContext: [{ kind: ContextKind.LabelName, metricName: 'metric_name' }], 260 }, 261 { 262 title: 'autocomplete labelName that defined a metric', 263 expr: '{}', 264 pos: 1, // cursor is between the bracket 265 expectedContext: [{ kind: ContextKind.LabelName, metricName: '' }], 266 }, 267 { 268 title: 'continue to autocomplete labelName associated to a metric', 269 expr: 'metric_name{myL}', 270 pos: 15, // cursor is between the bracket after the string myL 271 expectedContext: [{ kind: ContextKind.LabelName, metricName: 'metric_name' }], 272 }, 273 { 274 title: 'continue to autocomplete labelName associated to a metric 2', 275 expr: 'metric_name{myLabel="labelValue",}', 276 pos: 33, // cursor is between the bracket after the comma 277 expectedContext: [{ kind: ContextKind.LabelName, metricName: 'metric_name' }], 278 }, 279 { 280 title: 'continue autocomplete labelName that defined a metric', 281 expr: '{myL}', 282 pos: 4, // cursor is between the bracket after the string myL 283 expectedContext: [{ kind: ContextKind.LabelName, metricName: '' }], 284 }, 285 { 286 title: 'continue autocomplete labelName that defined a metric 2', 287 expr: '{myLabel="labelValue",}', 288 pos: 22, // cursor is between the bracket after the comma 289 expectedContext: [{ kind: ContextKind.LabelName, metricName: '' }], 290 }, 291 { 292 title: 'autocomplete the labelValue with metricName + labelName', 293 expr: 'metric_name{labelName=""}', 294 pos: 23, // cursor is between the quotes 295 expectedContext: [ 296 { 297 kind: ContextKind.LabelValue, 298 metricName: 'metric_name', 299 labelName: 'labelName', 300 matchers: [ 301 { 302 name: 'labelName', 303 type: EqlSingle, 304 value: '', 305 }, 306 ], 307 }, 308 ], 309 }, 310 { 311 title: 'autocomplete the labelValue with metricName + labelName 2', 312 expr: 'metric_name{labelName="labelValue", labelName!=""}', 313 pos: 48, // cursor is between the quotes 314 expectedContext: [ 315 { 316 kind: ContextKind.LabelValue, 317 metricName: 'metric_name', 318 labelName: 'labelName', 319 matchers: [ 320 { 321 name: 'labelName', 322 type: EqlSingle, 323 value: 'labelValue', 324 }, 325 { 326 name: 'labelName', 327 type: Neq, 328 value: '', 329 }, 330 ], 331 }, 332 ], 333 }, 334 { 335 title: 'autocomplete the labelValue associated to a labelName', 336 expr: '{labelName=""}', 337 pos: 12, // cursor is between the quotes 338 expectedContext: [ 339 { 340 kind: ContextKind.LabelValue, 341 metricName: '', 342 labelName: 'labelName', 343 matchers: [ 344 { 345 name: 'labelName', 346 type: EqlSingle, 347 value: '', 348 }, 349 ], 350 }, 351 ], 352 }, 353 { 354 title: 'autocomplete the labelValue associated to a labelName 2', 355 expr: '{labelName="labelValue", labelName!=""}', 356 pos: 37, // cursor is between the quotes 357 expectedContext: [ 358 { 359 kind: ContextKind.LabelValue, 360 metricName: '', 361 labelName: 'labelName', 362 matchers: [ 363 { 364 name: 'labelName', 365 type: EqlSingle, 366 value: 'labelValue', 367 }, 368 { 369 name: 'labelName', 370 type: Neq, 371 value: '', 372 }, 373 ], 374 }, 375 ], 376 }, 377 { 378 title: 'autocomplete AggregateOpModifier or BinOp', 379 expr: 'sum() b', 380 pos: 7, // cursor is after the 'b' 381 expectedContext: [{ kind: ContextKind.AggregateOpModifier }, { kind: ContextKind.BinOp }], 382 }, 383 { 384 title: 'autocomplete AggregateOpModifier or BinOp 2', 385 expr: 'sum(rate(foo[5m])) an', 386 pos: 21, 387 expectedContext: [{ kind: ContextKind.AggregateOpModifier }, { kind: ContextKind.BinOp }], 388 }, 389 { 390 title: 'autocomplete AggregateOpModifier or BinOp or Offset', 391 expr: 'sum b', 392 pos: 5, // cursor is after 'b' 393 expectedContext: [{ kind: ContextKind.AggregateOpModifier }, { kind: ContextKind.BinOp }, { kind: ContextKind.Offset }], 394 }, 395 { 396 title: 'autocomplete binOp', 397 expr: 'metric_name !', 398 pos: 13, 399 expectedContext: [{ kind: ContextKind.BinOp }], 400 }, 401 { 402 title: 'autocomplete binOp 2', 403 expr: 'metric_name =', 404 pos: 13, 405 expectedContext: [{ kind: ContextKind.BinOp }], 406 }, 407 { 408 title: 'autocomplete matchOp', 409 expr: 'go{instance=""}', 410 pos: 12, // cursor is after the 'equal' 411 expectedContext: [{ kind: ContextKind.MatchOp }], 412 }, 413 { 414 title: 'autocomplete matchOp 2', 415 expr: 'metric_name{labelName!}', 416 pos: 22, // cursor is after '!' 417 expectedContext: [{ kind: ContextKind.MatchOp }], 418 }, 419 { 420 title: 'autocomplete duration with offset', 421 expr: 'http_requests_total offset 5', 422 pos: 28, 423 expectedContext: [{ kind: ContextKind.Duration }], 424 }, 425 { 426 title: 'autocomplete duration with offset', 427 expr: 'sum(http_requests_total{method="GET"} offset 4)', 428 pos: 46, 429 expectedContext: [{ kind: ContextKind.Duration }], 430 }, 431 { 432 title: 'autocomplete offset or binOp', 433 expr: 'http_requests_total off', 434 pos: 23, 435 expectedContext: [{ kind: ContextKind.BinOp }, { kind: ContextKind.Offset }], 436 }, 437 { 438 title: 'autocomplete offset or binOp 2', 439 expr: 'metric_name unle', 440 pos: 16, 441 expectedContext: [{ kind: ContextKind.BinOp }, { kind: ContextKind.Offset }], 442 }, 443 { 444 title: 'autocomplete offset or binOp 3', 445 expr: 'http_requests_total{method="GET"} off', 446 pos: 37, 447 expectedContext: [{ kind: ContextKind.BinOp }, { kind: ContextKind.Offset }], 448 }, 449 { 450 title: 'autocomplete offset or binOp 4', 451 expr: 'rate(foo[5m]) un', 452 pos: 16, 453 expectedContext: [{ kind: ContextKind.BinOp }, { kind: ContextKind.Offset }], 454 }, 455 { 456 title: 'not autocompleting duration for a matrixSelector', 457 expr: 'go[]', 458 pos: 3, 459 expectedContext: [], 460 }, 461 { 462 title: 'not autocompleting duration for a matrixSelector 2', 463 expr: 'go{l1="l2"}[]', 464 pos: 12, 465 expectedContext: [], 466 }, 467 { 468 title: 'autocomplete duration for a matrixSelector', 469 expr: 'go[5]', 470 pos: 4, 471 expectedContext: [{ kind: ContextKind.Duration }], 472 }, 473 { 474 title: 'autocomplete duration for a matrixSelector 2', 475 expr: 'go[5d1]', 476 pos: 6, 477 expectedContext: [{ kind: ContextKind.Duration }], 478 }, 479 { 480 title: 'autocomplete duration for a matrixSelector 3', 481 expr: 'rate(my_metric{l1="l2"}[25])', 482 pos: 26, 483 expectedContext: [{ kind: ContextKind.Duration }], 484 }, 485 { 486 title: 'autocomplete duration for a matrixSelector 4', 487 expr: 'rate(my_metric{l1="l2"}[25d5])', 488 pos: 28, 489 expectedContext: [{ kind: ContextKind.Duration }], 490 }, 491 { 492 title: 'autocomplete duration for a subQuery', 493 expr: 'go[5d:5]', 494 pos: 7, 495 expectedContext: [{ kind: ContextKind.Duration }], 496 }, 497 { 498 title: 'autocomplete duration for a subQuery 2', 499 expr: 'go[5d:5d4]', 500 pos: 9, 501 expectedContext: [{ kind: ContextKind.Duration }], 502 }, 503 { 504 title: 'autocomplete duration for a subQuery 3', 505 expr: 'rate(my_metric{l1="l2"}[25d:6])', 506 pos: 29, 507 expectedContext: [{ kind: ContextKind.Duration }], 508 }, 509 { 510 title: 'autocomplete duration for a subQuery 4', 511 expr: 'rate(my_metric{l1="l2"}[25d:6d5])', 512 pos: 31, 513 expectedContext: [{ kind: ContextKind.Duration }], 514 }, 515 { 516 title: 'autocomplete at modifiers', 517 expr: '1 @ s', 518 pos: 5, 519 expectedContext: [{ kind: ContextKind.AtModifiers }], 520 }, 521 ]; 522 testCases.forEach((value) => { 523 it(value.title, () => { 524 const state = createEditorState(value.expr); 525 const node = syntaxTree(state).resolve(value.pos, -1); 526 const result = analyzeCompletion(state, node); 527 chai.expect(value.expectedContext).to.deep.equal(result); 528 }); 529 }); 530}); 531 532describe('computeStartCompletePosition test', () => { 533 const testCases = [ 534 { 535 title: 'empty bracket', 536 expr: '{}', 537 pos: 1, // cursor is between the bracket 538 expectedStart: 1, 539 }, 540 { 541 title: 'empty bracket 2', 542 expr: 'metricName{}', 543 pos: 11, // cursor is between the bracket 544 expectedStart: 11, 545 }, 546 { 547 title: 'empty bracket 3', 548 expr: 'sum by()', 549 pos: 7, // cursor is between the bracket 550 expectedStart: 7, 551 }, 552 { 553 title: 'empty bracket 4', 554 expr: 'sum by(test) ()', 555 pos: 14, // cursor is between the bracket 556 expectedStart: 14, 557 }, 558 { 559 title: 'empty bracket 5', 560 expr: 'sum()', 561 pos: 4, // cursor is between the bracket 562 expectedStart: 4, 563 }, 564 { 565 title: 'empty bracket 6', 566 expr: 'sum(rate())', 567 pos: 9, // cursor is between the bracket 568 expectedStart: 9, 569 }, 570 { 571 title: 'bracket containing a substring', 572 expr: '{myL}', 573 pos: 4, // cursor is between the bracket 574 expectedStart: 1, 575 }, 576 { 577 title: 'bracket containing a substring 2', 578 expr: '{myLabel="LabelValue",}', 579 pos: 22, // cursor is after the comma 580 expectedStart: 22, 581 }, 582 { 583 title: 'bracket containing a substring 2', 584 expr: 'metricName{myL}', 585 pos: 14, // cursor is between the bracket 586 expectedStart: 11, 587 }, 588 { 589 title: 'bracket containing a substring 3', 590 expr: 'metricName{myLabel="LabelValue",}', 591 pos: 32, // cursor is after the comma 592 expectedStart: 32, 593 }, 594 { 595 title: 'bracket containing a substring 4', 596 expr: 'sum by(myL)', 597 pos: 10, // cursor is between the bracket 598 expectedStart: 7, 599 }, 600 { 601 title: 'bracket containing a substring 5', 602 expr: 'sum by(myLabel,)', 603 pos: 15, // cursor is after the comma 604 expectedStart: 15, 605 }, 606 { 607 title: 'bracket containing a substring 6', 608 expr: 'sum(ra)', 609 pos: 6, // cursor is between the bracket 610 expectedStart: 4, 611 }, 612 { 613 title: 'bracket containing a substring 7', 614 expr: 'sum(rate(my))', 615 pos: 11, // cursor is between the bracket 616 expectedStart: 9, 617 }, 618 { 619 title: 'start should not be at the beginning of the substring', 620 expr: 'metric_name{labelName!}', 621 pos: 22, 622 expectedStart: 21, 623 }, 624 { 625 title: 'start should not be at the beginning of the substring 2', 626 expr: 'metric_name{labelName!="labelValue"}', 627 pos: 22, 628 expectedStart: 21, 629 }, 630 { 631 title: 'start should be equal to the pos for the duration of an offset', 632 expr: 'http_requests_total offset 5', 633 pos: 28, 634 expectedStart: 28, 635 }, 636 { 637 title: 'start should be equal to the pos for the duration of an offset 2', 638 expr: 'http_requests_total offset 587', 639 pos: 30, 640 expectedStart: 30, 641 }, 642 { 643 title: 'start should be equal to the pos for the duration of an offset 3', 644 expr: 'http_requests_total offset 587', 645 pos: 29, 646 expectedStart: 29, 647 }, 648 { 649 title: 'start should be equal to the pos for the duration of an offset 4', 650 expr: 'sum(http_requests_total{method="GET"} offset 4)', 651 pos: 46, 652 expectedStart: 46, 653 }, 654 { 655 title: 'start should not be equal to the pos for the duration in a matrix selector', 656 expr: 'go[]', 657 pos: 3, 658 expectedStart: 0, 659 }, 660 { 661 title: 'start should be equal to the pos for the duration in a matrix selector', 662 expr: 'go[5]', 663 pos: 4, 664 expectedStart: 4, 665 }, 666 { 667 title: 'start should be equal to the pos for the duration in a matrix selector 2', 668 expr: 'go[5d5]', 669 pos: 6, 670 expectedStart: 6, 671 }, 672 { 673 title: 'start should be equal to the pos for the duration in a matrix selector 3', 674 expr: 'rate(my_metric{l1="l2"}[25])', 675 pos: 26, 676 expectedStart: 26, 677 }, 678 { 679 title: 'start should be equal to the pos for the duration in a matrix selector 4', 680 expr: 'rate(my_metric{l1="l2"}[25d5])', 681 pos: 28, 682 expectedStart: 28, 683 }, 684 { 685 title: 'start should be equal to the pos for the duration in a subquery selector', 686 expr: 'go[5d:5]', 687 pos: 7, 688 expectedStart: 7, 689 }, 690 { 691 title: 'start should be equal to the pos for the duration in a subquery selector 2', 692 expr: 'go[5d:5d5]', 693 pos: 9, 694 expectedStart: 9, 695 }, 696 { 697 title: 'start should be equal to the pos for the duration in a subquery selector 3', 698 expr: 'rate(my_metric{l1="l2"}[25d:6])', 699 pos: 29, 700 expectedStart: 29, 701 }, 702 { 703 title: 'start should be equal to the pos for the duration in a subquery selector 3', 704 expr: 'rate(my_metric{l1="l2"}[25d:6d5])', 705 pos: 31, 706 expectedStart: 31, 707 }, 708 ]; 709 testCases.forEach((value) => { 710 it(value.title, () => { 711 const state = createEditorState(value.expr); 712 const node = syntaxTree(state).resolve(value.pos, -1); 713 const result = computeStartCompletePosition(node, value.pos); 714 chai.expect(value.expectedStart).to.equal(result); 715 }); 716 }); 717}); 718 719describe('autocomplete promQL test', () => { 720 beforeEach(() => { 721 mockPrometheusServer(); 722 }); 723 const testCases = [ 724 { 725 title: 'offline empty expr', 726 expr: '', 727 pos: 0, 728 expectedResult: { 729 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, numberTerms, snippets), 730 from: 0, 731 to: 0, 732 span: /^[a-zA-Z0-9_:]+$/, 733 }, 734 }, 735 { 736 title: 'offline simple function/aggregation/number autocompletion', 737 expr: 'go_', 738 pos: 3, 739 expectedResult: { 740 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, numberTerms, snippets), 741 from: 0, 742 to: 3, 743 span: /^[a-zA-Z0-9_:]+$/, 744 }, 745 }, 746 { 747 title: 'offline function/aggregation autocompletion in aggregation', 748 expr: 'sum()', 749 pos: 4, 750 expectedResult: { 751 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, snippets), 752 from: 4, 753 to: 4, 754 span: /^[a-zA-Z0-9_:]+$/, 755 }, 756 }, 757 { 758 title: 'offline function/aggregation autocompletion in aggregation 2', 759 expr: 'sum(ra)', 760 pos: 6, 761 expectedResult: { 762 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, snippets), 763 from: 4, 764 to: 6, 765 span: /^[a-zA-Z0-9_:]+$/, 766 }, 767 }, 768 { 769 title: 'offline function/aggregation autocompletion in aggregation 3', 770 expr: 'sum(rate())', 771 pos: 9, 772 expectedResult: { 773 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, snippets), 774 from: 9, 775 to: 9, 776 span: /^[a-zA-Z0-9_:]+$/, 777 }, 778 }, 779 { 780 title: 'offline function/aggregation autocompletion in aggregation 4', 781 expr: 782 'sum by (instance, job) ( sum_over(scrape_series_added[1h])) / sum by (instance, job) (sum_over_time(scrape_samples_scraped[1h])) > 0.1 and sum by(instance, job) (scrape_samples_scraped{) > 100', 783 pos: 33, 784 expectedResult: { 785 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, snippets), 786 from: 25, 787 to: 33, 788 span: /^[a-zA-Z0-9_:]+$/, 789 }, 790 }, 791 { 792 title: 'autocomplete binOp modifier/metric/number', 793 expr: 'metric_name / ignor', 794 pos: 19, 795 expectedResult: { 796 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, binOpModifierTerms, numberTerms, snippets), 797 from: 14, 798 to: 19, 799 span: /^[a-zA-Z0-9_:]+$/, 800 }, 801 }, 802 { 803 title: 'autocomplete binOp modifier/metric/number 2', 804 expr: 'sum(http_requests_total{method="GET"} / o)', 805 pos: 41, 806 expectedResult: { 807 options: ([] as Completion[]).concat(functionIdentifierTerms, aggregateOpTerms, binOpModifierTerms, numberTerms, snippets), 808 from: 40, 809 to: 41, 810 span: /^[a-zA-Z0-9_:]+$/, 811 }, 812 }, 813 { 814 title: 'offline autocomplete labelName return nothing', 815 expr: 'sum by ()', 816 pos: 8, // cursor is between the bracket 817 expectedResult: { 818 options: [], 819 from: 8, 820 to: 8, 821 span: /^[a-zA-Z0-9_:]+$/, 822 }, 823 }, 824 { 825 title: 'offline autocomplete labelName return nothing 2', 826 expr: 'sum by (myL)', 827 pos: 11, // cursor is between the bracket after the string myL 828 expectedResult: { 829 options: [], 830 from: 8, 831 to: 11, 832 span: /^[a-zA-Z0-9_:]+$/, 833 }, 834 }, 835 { 836 title: 'offline autocomplete labelName return nothing 3', 837 expr: 'sum by (myLabel1, myLab)', 838 pos: 23, // cursor is between the bracket after the string myLab 839 expectedResult: { 840 options: [], 841 from: 18, 842 to: 23, 843 span: /^[a-zA-Z0-9_:]+$/, 844 }, 845 }, 846 { 847 title: 'offline autocomplete labelName return nothing 4', 848 expr: 'metric_name{}', 849 pos: 12, // cursor is between the bracket 850 expectedResult: { 851 options: [], 852 from: 12, 853 to: 12, 854 span: /^[a-zA-Z0-9_:]+$/, 855 }, 856 }, 857 { 858 title: 'offline autocomplete labelName return nothing 5', 859 expr: '{}', 860 pos: 1, // cursor is between the bracket 861 expectedResult: { 862 options: [], 863 from: 1, 864 to: 1, 865 span: /^[a-zA-Z0-9_:]+$/, 866 }, 867 }, 868 { 869 title: 'offline autocomplete labelName return nothing 6', 870 expr: 'metric_name{myL}', 871 pos: 15, // cursor is between the bracket after the string myL 872 expectedResult: { 873 options: [], 874 from: 12, 875 to: 15, 876 span: /^[a-zA-Z0-9_:]+$/, 877 }, 878 }, 879 { 880 title: 'offline autocomplete labelName return nothing 7', 881 expr: '{myL}', 882 pos: 4, // cursor is between the bracket after the string myL 883 expectedResult: { 884 options: [], 885 from: 1, 886 to: 4, 887 span: /^[a-zA-Z0-9_:]+$/, 888 }, 889 }, 890 { 891 title: 'offline autocomplete labelValue return nothing', 892 expr: 'metric_name{labelName=""}', 893 pos: 23, // cursor is between the quotes 894 expectedResult: { 895 options: [], 896 from: 23, 897 to: 23, 898 span: /^[a-zA-Z0-9_:]+$/, 899 }, 900 }, 901 { 902 title: 'offline autocomplete labelValue return nothing 2', 903 expr: '{labelName=""}', 904 pos: 12, // cursor is between the quotes 905 expectedResult: { 906 options: [], 907 from: 12, 908 to: 12, 909 span: /^[a-zA-Z0-9_:]+$/, 910 }, 911 }, 912 { 913 title: 'offline autocomplete aggregate operation modifier or binary operator', 914 expr: 'sum() b', 915 pos: 7, // cursor is after 'b' 916 expectedResult: { 917 options: ([] as Completion[]).concat(aggregateOpModifierTerms, binOpTerms), 918 from: 6, 919 to: 7, 920 span: /^[a-zA-Z0-9_:]+$/, 921 }, 922 }, 923 { 924 title: 'offline autocomplete aggregate operation modifier or binary operator 2', 925 expr: 'sum(rate(foo[5m])) an', 926 pos: 21, // cursor is after the string 'an' 927 expectedResult: { 928 options: ([] as Completion[]).concat(aggregateOpModifierTerms, binOpTerms), 929 from: 19, 930 to: 21, 931 span: /^[a-zA-Z0-9_:]+$/, 932 }, 933 }, 934 { 935 title: 'offline autocomplete aggregate operation modifier or binary operator or offset', 936 expr: 'sum b', 937 pos: 5, // cursor is after 'b' 938 expectedResult: { 939 options: ([] as Completion[]).concat(aggregateOpModifierTerms, binOpTerms, [{ label: 'offset' }]), 940 from: 4, 941 to: 5, 942 span: /^[a-zA-Z0-9_:]+$/, 943 }, 944 }, 945 { 946 title: 'offline autocomplete binOp', 947 expr: 'metric_name !', 948 pos: 13, 949 expectedResult: { 950 options: binOpTerms, 951 from: 12, 952 to: 13, 953 span: /^[a-zA-Z0-9_:]+$/, 954 }, 955 }, 956 { 957 title: 'offline autocomplete binOp 2', 958 expr: 'metric_name =', 959 pos: 13, 960 expectedResult: { 961 options: binOpTerms, 962 from: 12, 963 to: 13, 964 span: /^[a-zA-Z0-9_:]+$/, 965 }, 966 }, 967 { 968 title: 'offline autocomplete matchOp', 969 expr: 'go{instance=""}', 970 pos: 12, // cursor is after the 'equal' 971 expectedResult: { 972 options: matchOpTerms, 973 from: 11, 974 to: 12, 975 span: /^[a-zA-Z0-9_:]+$/, 976 }, 977 }, 978 { 979 title: 'offline autocomplete matchOp 2', 980 expr: 'metric_name{labelName!}', 981 pos: 22, // cursor is after '!' 982 expectedResult: { 983 options: matchOpTerms, 984 from: 21, 985 to: 22, 986 span: /^[a-zA-Z0-9_:]+$/, 987 }, 988 }, 989 { 990 title: 'offline autocomplete duration with offset', 991 expr: 'http_requests_total offset 5', 992 pos: 28, 993 expectedResult: { 994 options: durationTerms, 995 from: 28, 996 to: 28, 997 span: undefined, 998 }, 999 }, 1000 { 1001 title: 'offline autocomplete duration with offset 2', 1002 expr: 'sum(http_requests_total{method="GET"} offset 4)', 1003 pos: 46, 1004 expectedResult: { 1005 options: durationTerms, 1006 from: 46, 1007 to: 46, 1008 span: undefined, 1009 }, 1010 }, 1011 { 1012 title: 'offline autocomplete offset or binOp', 1013 expr: 'http_requests_total off', 1014 pos: 23, 1015 expectedResult: { 1016 options: ([] as Completion[]).concat(binOpTerms, [{ label: 'offset' }]), 1017 from: 20, 1018 to: 23, 1019 span: /^[a-zA-Z0-9_:]+$/, 1020 }, 1021 }, 1022 { 1023 title: 'offline autocomplete offset or binOp 2', 1024 expr: 'metric_name unle', 1025 pos: 16, 1026 expectedResult: { 1027 options: ([] as Completion[]).concat(binOpTerms, [{ label: 'offset' }]), 1028 from: 12, 1029 to: 16, 1030 span: /^[a-zA-Z0-9_:]+$/, 1031 }, 1032 }, 1033 { 1034 title: 'offline autocomplete offset or binOp 3', 1035 expr: 'http_requests_total{method="GET"} off', 1036 pos: 37, 1037 expectedResult: { 1038 options: ([] as Completion[]).concat(binOpTerms, [{ label: 'offset' }]), 1039 from: 34, 1040 to: 37, 1041 span: /^[a-zA-Z0-9_:]+$/, 1042 }, 1043 }, 1044 { 1045 title: 'offline autocomplete offset or binOp 4', 1046 expr: 'rate(foo[5m]) un', 1047 pos: 16, 1048 expectedResult: { 1049 options: ([] as Completion[]).concat(binOpTerms, [{ label: 'offset' }]), 1050 from: 14, 1051 to: 16, 1052 span: /^[a-zA-Z0-9_:]+$/, 1053 }, 1054 }, 1055 { 1056 title: 'offline not autocompleting duration for a matrixSelector', 1057 expr: 'go[]', 1058 pos: 3, 1059 expectedResult: { 1060 options: [], 1061 from: 0, 1062 to: 3, 1063 span: /^[a-zA-Z0-9_:]+$/, 1064 }, 1065 }, 1066 { 1067 title: 'offline not autocompleting duration for a matrixSelector 2', 1068 expr: 'go{l1="l2"}[]', 1069 pos: 12, 1070 expectedResult: { 1071 options: [], 1072 from: 0, 1073 to: 12, 1074 span: /^[a-zA-Z0-9_:]+$/, 1075 }, 1076 }, 1077 { 1078 title: 'offline autocomplete duration for a matrixSelector', 1079 expr: 'go[5]', 1080 pos: 4, 1081 expectedResult: { 1082 options: durationTerms, 1083 from: 4, 1084 to: 4, 1085 span: undefined, 1086 }, 1087 }, 1088 { 1089 title: 'offline autocomplete duration for a matrixSelector 2', 1090 expr: 'go[5d1]', 1091 pos: 6, 1092 expectedResult: { 1093 options: durationTerms, 1094 from: 6, 1095 to: 6, 1096 span: undefined, 1097 }, 1098 }, 1099 { 1100 title: 'offline autocomplete duration for a matrixSelector 3', 1101 expr: 'rate(my_metric{l1="l2"}[25])', 1102 pos: 26, 1103 expectedResult: { 1104 options: durationTerms, 1105 from: 26, 1106 to: 26, 1107 span: undefined, 1108 }, 1109 }, 1110 { 1111 title: 'offline autocomplete duration for a matrixSelector 4', 1112 expr: 'rate(my_metric{l1="l2"}[25d5])', 1113 pos: 28, 1114 expectedResult: { 1115 options: durationTerms, 1116 from: 28, 1117 to: 28, 1118 span: undefined, 1119 }, 1120 }, 1121 { 1122 title: 'offline autocomplete duration for a subQuery', 1123 expr: 'go[5d:5]', 1124 pos: 7, 1125 expectedResult: { 1126 options: durationTerms, 1127 from: 7, 1128 to: 7, 1129 span: undefined, 1130 }, 1131 }, 1132 { 1133 title: 'offline autocomplete duration for a subQuery 2', 1134 expr: 'go[5d:5d4]', 1135 pos: 9, 1136 expectedResult: { 1137 options: durationTerms, 1138 from: 9, 1139 to: 9, 1140 span: undefined, 1141 }, 1142 }, 1143 { 1144 title: 'offline autocomplete duration for a subQuery 3', 1145 expr: 'rate(my_metric{l1="l2"}[25d:6])', 1146 pos: 29, 1147 expectedResult: { 1148 options: durationTerms, 1149 from: 29, 1150 to: 29, 1151 span: undefined, 1152 }, 1153 }, 1154 { 1155 title: 'offline autocomplete duration for a subQuery 4', 1156 expr: 'rate(my_metric{l1="l2"}[25d:6d5])', 1157 pos: 31, 1158 expectedResult: { 1159 options: durationTerms, 1160 from: 31, 1161 to: 31, 1162 span: undefined, 1163 }, 1164 }, 1165 { 1166 title: 'offline autocomplete at modifiers', 1167 expr: '1 @ s', 1168 pos: 5, 1169 expectedResult: { 1170 options: atModifierTerms, 1171 from: 4, 1172 to: 5, 1173 span: /^[a-zA-Z0-9_:]+$/, 1174 }, 1175 }, 1176 { 1177 title: 'online autocomplete of metrics', 1178 expr: 'alert', 1179 pos: 5, 1180 conf: { remote: { url: 'http://localhost:8080' } }, 1181 expectedResult: { 1182 options: ([] as Completion[]).concat(mockedMetricsTerms, functionIdentifierTerms, aggregateOpTerms, numberTerms, snippets), 1183 from: 0, 1184 to: 5, 1185 span: /^[a-zA-Z0-9_:]+$/, 1186 }, 1187 }, 1188 { 1189 title: 'online autocomplete of label name corresponding to a metric', 1190 expr: 'alertmanager_alerts{}', 1191 pos: 20, 1192 conf: { remote: { url: 'http://localhost:8080' } }, 1193 expectedResult: { 1194 options: [ 1195 { 1196 label: 'env', 1197 type: 'constant', 1198 }, 1199 { 1200 label: 'instance', 1201 type: 'constant', 1202 }, 1203 { 1204 label: 'job', 1205 type: 'constant', 1206 }, 1207 { 1208 label: 'state', 1209 type: 'constant', 1210 }, 1211 ], 1212 from: 20, 1213 to: 20, 1214 span: /^[a-zA-Z0-9_:]+$/, 1215 }, 1216 }, 1217 { 1218 title: 'online autocomplete of label value corresponding to a metric and a label name', 1219 expr: 'alertmanager_alerts{env=""}', 1220 pos: 25, 1221 conf: { remote: { url: 'http://localhost:8080' } }, 1222 expectedResult: { 1223 options: [ 1224 { 1225 label: 'demo', 1226 type: 'text', 1227 }, 1228 ], 1229 from: 25, 1230 to: 25, 1231 span: /^[a-zA-Z0-9_:]+$/, 1232 }, 1233 }, 1234 ]; 1235 testCases.forEach((value) => { 1236 it(value.title, async () => { 1237 const state = createEditorState(value.expr); 1238 const context = new CompletionContext(state, value.pos, true); 1239 const completion = newCompleteStrategy(value.conf); 1240 const result = await completion.promQL(context); 1241 chai.expect(value.expectedResult).to.deep.equal(result); 1242 }); 1243 }); 1244}); 1245