+
diff --git a/client/app/services/query-result.js b/client/app/services/query-result.js
index 44839a7f10..6885ee5d4c 100644
--- a/client/app/services/query-result.js
+++ b/client/app/services/query-result.js
@@ -35,9 +35,10 @@ function getColumnFriendlyName(column) {
return getColumnNameWithoutType(column).replace(/(?:^|\s)\S/g, a => a.toUpperCase());
}
-function QueryResultService($resource, $timeout, $q, QueryResultError) {
+function QueryResultService($resource, $timeout, $q, QueryResultError, Auth) {
const QueryResultResource = $resource('api/query_results/:id', { id: '@id' }, { post: { method: 'POST' } });
const Job = $resource('api/jobs/:id', { id: '@id' });
+ const JobWithApiKey = $resource('api/queries/:queryId/jobs/:id', { queryId: '@queryId', id: '@id' });
const statuses = {
1: 'waiting',
2: 'processing',
@@ -293,6 +294,13 @@ function QueryResultService($resource, $timeout, $q, QueryResultError) {
return queryResult;
}
+ loadLatestCachedResult(queryId, parameters) {
+ $resource('api/queries/:id/results', { id: '@queryId' }, { post: { method: 'POST' } })
+ .post({ queryId, parameters },
+ (response) => { this.update(response); },
+ (error) => { handleErrorResponse(this, error); });
+ }
+
loadResult(tryCount) {
this.isLoadingResult = true;
QueryResultResource.get(
@@ -324,18 +332,23 @@ function QueryResultService($resource, $timeout, $q, QueryResultError) {
);
}
- refreshStatus(query, tryNumber = 1) {
- Job.get(
- { id: this.job.id },
+ refreshStatus(query, parameters, tryNumber = 1) {
+ const resource = Auth.isAuthenticated() ? Job : JobWithApiKey;
+ const loadResult = () => (Auth.isAuthenticated()
+ ? this.loadResult()
+ : this.loadLatestCachedResult(query, parameters));
+
+ resource.get(
+ { queryId: query, id: this.job.id },
(jobResponse) => {
this.update(jobResponse);
if (this.getStatus() === 'processing' && this.job.query_result_id && this.job.query_result_id !== 'None') {
- this.loadResult();
+ loadResult();
} else if (this.getStatus() !== 'failed') {
const waitTime = tryNumber > 10 ? 3000 : 500;
$timeout(() => {
- this.refreshStatus(query, tryNumber + 1);
+ this.refreshStatus(query, parameters, tryNumber + 1);
}, waitTime);
}
},
@@ -377,7 +390,7 @@ function QueryResultService($resource, $timeout, $q, QueryResultError) {
queryResult.update(response);
if ('job' in response) {
- queryResult.refreshStatus(id);
+ queryResult.refreshStatus(id, parameters);
}
},
(error) => {
@@ -408,7 +421,7 @@ function QueryResultService($resource, $timeout, $q, QueryResultError) {
queryResult.update(response);
if ('job' in response) {
- queryResult.refreshStatus(query);
+ queryResult.refreshStatus(query, parameters);
}
},
(error) => {
diff --git a/client/app/services/query.js b/client/app/services/query.js
index 3b012e9fdb..75f8b16d77 100644
--- a/client/app/services/query.js
+++ b/client/app/services/query.js
@@ -473,6 +473,10 @@ function QueryResource(
return this.getParameters().isRequired();
};
+ QueryService.prototype.hasParameters = function hasParameters() {
+ return this.getParametersDefs().length > 0;
+ };
+
QueryService.prototype.prepareQueryResultExecution = function prepareQueryResultExecution(execute, maxAge) {
if (!this.query) {
return new QueryResultError("Can't execute empty query.");
diff --git a/client/cypress/integration/embed/share_embed_spec.js b/client/cypress/integration/embed/share_embed_spec.js
index 193a278658..87230b60b3 100644
--- a/client/cypress/integration/embed/share_embed_spec.js
+++ b/client/cypress/integration/embed/share_embed_spec.js
@@ -7,7 +7,7 @@ describe('Embedded Queries', () => {
it('are shared with safe parameters', () => {
cy.getByTestId('QueryEditor')
.get('.ace_text-input')
- .type('SELECT name, slug FROM organizations WHERE id=\'{{}{{}id}}\'{esc}', { force: true });
+ .type("SELECT name, slug FROM organizations WHERE id='{{}{{}id}}'{esc}", { force: true });
cy.getByTestId('TextParamInput').type('1');
cy.clickThrough(`
@@ -25,19 +25,22 @@ describe('Embedded Queries', () => {
ShowEmbedDialogButton
`);
- cy.getByTestId('EmbedIframe').invoke('text').then((iframe) => {
- const embedUrl = iframe.match(/"(.*?)"/)[1];
- cy.logout();
- cy.visit(embedUrl);
- cy.getByTestId('VisualizationEmbed', { timeout: 10000 }).should('exist');
- cy.percySnapshot('Successfully Embedded Parameterized Query');
- });
+ cy.getByTestId('EmbedIframe')
+ .invoke('text')
+ .then((iframe) => {
+ const embedUrl = iframe.match(/"(.*?)"/)[1];
+ cy.logout();
+ cy.visit(embedUrl);
+ cy.getByTestId('VisualizationEmbed', { timeout: 10000 }).should('exist');
+ cy.getByTestId('TimeAgo', { timeout: 10000 }).should('exist');
+ cy.percySnapshot('Successfully Embedded Parameterized Query');
+ });
});
it('cannot be shared with unsafe parameters', () => {
cy.getByTestId('QueryEditor')
.get('.ace_text-input')
- .type('SELECT name, slug FROM organizations WHERE name=\'{{}{{}name}}\'{esc}', { force: true });
+ .type("SELECT name, slug FROM organizations WHERE name='{{}{{}name}}'{esc}", { force: true });
cy.getByTestId('TextParamInput').type('Redash');
cy.clickThrough(`
@@ -49,19 +52,22 @@ describe('Embedded Queries', () => {
SaveButton
`);
-
cy.location('search').should('eq', '?p_name=Redash');
cy.clickThrough(`
QueryControlDropdownButton
ShowEmbedDialogButton
`);
- cy.getByTestId('EmbedIframe').invoke('text').then((iframe) => {
- const embedUrl = iframe.match(/"(.*?)"/)[1];
- cy.logout();
- cy.visit(embedUrl, { failOnStatusCode: false }); // prevent 403 from failing test
- cy.getByTestId('ErrorMessage', { timeout: 10000 }).should('exist');
- cy.percySnapshot('Unsuccessfully Embedded Parameterized Query');
- });
+ cy.getByTestId('EmbedIframe')
+ .invoke('text')
+ .then((iframe) => {
+ const embedUrl = iframe.match(/"(.*?)"/)[1];
+ cy.logout();
+ cy.visit(embedUrl, { failOnStatusCode: false }); // prevent 403 from failing test
+ cy.getByTestId('ErrorMessage', { timeout: 10000 })
+ .should('exist')
+ .contains("Can't embed");
+ cy.percySnapshot('Unsuccessfully Embedded Parameterized Query');
+ });
});
});
diff --git a/redash/handlers/api.py b/redash/handlers/api.py
index 6de8802c57..012c121f63 100644
--- a/redash/handlers/api.py
+++ b/redash/handlers/api.py
@@ -129,7 +129,10 @@ def json_representation(data, code, headers=None):
'/api/queries/
/results.',
'/api/queries//results/.',
endpoint='query_result')
-api.add_org_resource(JobResource, '/api/jobs/', endpoint='job')
+api.add_org_resource(JobResource,
+ '/api/jobs/',
+ '/api/queries//jobs/',
+ endpoint='job')
api.add_org_resource(UserListResource, '/api/users', endpoint='users')
api.add_org_resource(UserResource, '/api/users/', endpoint='user')
diff --git a/redash/handlers/query_results.py b/redash/handlers/query_results.py
index 4d089061db..1ec78a0534 100644
--- a/redash/handlers/query_results.py
+++ b/redash/handlers/query_results.py
@@ -284,7 +284,7 @@ def make_excel_response(query_result):
class JobResource(BaseResource):
- def get(self, job_id):
+ def get(self, job_id, query_id=None):
"""
Retrieve info about a running query job.
"""