Explorar o código

graphite: fix nested alerting queries (#10633)

Alexander Zobnin %!s(int64=8) %!d(string=hai) anos
pai
achega
e6c19eb8e9

+ 21 - 1
public/app/plugins/datasource/graphite/graphite_query.ts

@@ -181,6 +181,22 @@ export default class GraphiteQuery {
     var nestedSeriesRefRegex = /\#([A-Z])/g;
     var targetWithNestedQueries = target.target;
 
+    // Use ref count to track circular references
+    function countTargetRefs(targetsByRefId, refId) {
+      let refCount = 0;
+      _.each(targetsByRefId, (t, id) => {
+        if (id !== refId) {
+          let match = nestedSeriesRefRegex.exec(t.target);
+          let count = match && match.length ? match.length - 1 : 0;
+          refCount += count;
+        }
+      });
+      targetsByRefId[refId].refCount = refCount;
+    }
+    _.each(targetsByRefId, (t, id) => {
+      countTargetRefs(targetsByRefId, id);
+    });
+
     // Keep interpolating until there are no query references
     // The reason for the loop is that the referenced query might contain another reference to another query
     while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
@@ -191,7 +207,11 @@ export default class GraphiteQuery {
         }
 
         // no circular references
-        delete targetsByRefId[g1];
+        if (t.refCount === 0) {
+          delete targetsByRefId[g1];
+        }
+        t.refCount--;
+
         return t.target;
       });
 

+ 47 - 0
public/app/plugins/datasource/graphite/specs/graphite_query.jest.ts

@@ -0,0 +1,47 @@
+import gfunc from '../gfunc';
+import GraphiteQuery from '../graphite_query';
+
+describe('Graphite query model', () => {
+  let ctx: any = {
+    datasource: {
+      getFuncDef: gfunc.getFuncDef,
+      getFuncDefs: jest.fn().mockReturnValue(Promise.resolve(gfunc.getFuncDefs('1.0'))),
+      waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)),
+      createFuncInstance: gfunc.createFuncInstance,
+    },
+    templateSrv: {},
+    targets: [],
+  };
+
+  beforeEach(() => {
+    ctx.target = { refId: 'A', target: 'scaleToSeconds(#A, 60)' };
+    ctx.queryModel = new GraphiteQuery(ctx.datasource, ctx.target, ctx.templateSrv);
+  });
+
+  describe('when updating targets with nested queries', () => {
+    beforeEach(() => {
+      ctx.target = { refId: 'D', target: 'asPercent(#A, #C)' };
+      ctx.targets = [
+        { refId: 'A', target: 'first.query.count' },
+        { refId: 'B', target: 'second.query.count' },
+        { refId: 'C', target: 'diffSeries(#A, #B)' },
+        { refId: 'D', target: 'asPercent(#A, #C)' },
+      ];
+      ctx.queryModel = new GraphiteQuery(ctx.datasource, ctx.target, ctx.templateSrv);
+    });
+
+    it('targetFull should include nested queries', () => {
+      ctx.queryModel.updateRenderedTarget(ctx.target, ctx.targets);
+      const targetFullExpected = 'asPercent(first.query.count, diffSeries(first.query.count, second.query.count))';
+      expect(ctx.queryModel.target.targetFull).toBe(targetFullExpected);
+    });
+
+    it('should not hang on circular references', () => {
+      ctx.target.target = 'asPercent(#A, #B)';
+      ctx.targets = [{ refId: 'A', target: 'asPercent(#B, #C)' }, { refId: 'B', target: 'asPercent(#A, #C)' }];
+      ctx.queryModel.updateRenderedTarget(ctx.target, ctx.targets);
+      // Just ensure updateRenderedTarget() is completed and doesn't hang
+      expect(ctx.queryModel.target.targetFull).toBeDefined();
+    });
+  });
+});