Skip to content

Commit

Permalink
Query: make Public, Private, Archive & Unarchive (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored Feb 16, 2024
1 parent 8a1143b commit a6dc8de
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 34 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ jobs:
test:
runs-on: ubuntu-latest
env:
DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }}
BASIC_API_KEY: ${{ secrets.BASIC_API_KEY }}
PLUS_API_KEY: ${{ secrets.PLUS_API_KEY }}
strategy:
matrix:
node-version: [20.x]
Expand Down
44 changes: 36 additions & 8 deletions src/api/query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Assuming the existence of these imports based on your Python code
import { Router } from "./router";
import { DuneQuery, QueryParameter, CreateQueryResponse } from "../types";
import { DuneQuery, QueryParameter, CreateQueryResponse, DuneError } from "../types";
import { CreateQueryPayload, UpdateQueryPayload } from "../types/requestPayload";

interface EditQueryResponse {
query_id: number;
}
export class QueryAPI extends Router {
/**
* Creates a Dune Query by ID
Expand All @@ -20,13 +23,8 @@ export class QueryAPI extends Router {
is_private: isPrivate,
query_parameters: params ? params : [],
};
try {
const responseJson = await this._post<CreateQueryResponse>("query/", payload);
return this.getQuery(responseJson.query_id);
} catch (err: unknown) {
throw new Error(`Fokin Broken: ${err}`);
// throw new DuneError(responseJson, "CreateQueryResponse", err);
}
const responseJson = await this._post<CreateQueryResponse>("query/", payload);
return this.getQuery(responseJson.query_id);
}

/**
Expand Down Expand Up @@ -73,4 +71,34 @@ export class QueryAPI extends Router {
// throw new DuneError(responseJson, "UpdateQueryResponse", err);
}
}

public async archiveQuery(queryId: number): Promise<boolean> {
const response = await this._post<EditQueryResponse>(`/query/${queryId}/archive`);
const query = await this.getQuery(response.query_id);
return query.is_archived;
}

public async unarchiveQuery(queryId: number): Promise<boolean> {
const response = await this._post<EditQueryResponse>(`/query/${queryId}/unarchive`);
const query = await this.getQuery(response.query_id);
return query.is_archived;
}

public async makePrivate(queryId: number): Promise<void> {
const response = await this._post<EditQueryResponse>(`/query/${queryId}/private`);
const query = await this.getQuery(response.query_id);
if (!query.is_private) {
throw new DuneError("Query was not made private!");
}
}

public async makePublic(queryId: number): Promise<void> {
const responseJson = await this._post<EditQueryResponse>(
`/query/${queryId}/unprivate`,
);
const query = await this.getQuery(responseJson.query_id);
if (query.is_private) {
throw new DuneError("Query is still private.");
}
}
}
2 changes: 1 addition & 1 deletion src/api/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum RequestMethod {

// This class implements all the routes defined in the Dune API Docs: https://dune.com/docs/api/
export class Router {
apiKey: string;
private apiKey: string;

constructor(apiKey: string) {
this.apiKey = apiKey;
Expand Down
10 changes: 7 additions & 3 deletions tests/e2e/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { expect } from "chai";
import { DuneClient, QueryParameter } from "../../src/";
import log from "loglevel";
import { apiKey } from "./util";
import { BASIC_KEY } from "./util";

log.setLevel("silent", true);

describe("DuneClient Extensions", () => {
let client: DuneClient;

beforeEach(() => {
client = new DuneClient(BASIC_KEY);
});

it("execute runQuery", async () => {
const client = new DuneClient(apiKey);
// https://dune.com/queries/1215383
const results = await client.runQuery(1215383, {
query_parameters: [QueryParameter.text("TextField", "Plain Text")],
Expand All @@ -23,7 +28,6 @@ describe("DuneClient Extensions", () => {
});

it("getsLatestResults", async () => {
const client = new DuneClient(apiKey);
// https://dune.com/queries/1215383
const results = await client.getLatestResult(1215383, [
QueryParameter.text("TextField", "Plain Text"),
Expand Down
31 changes: 18 additions & 13 deletions tests/e2e/executionAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import { expect } from "chai";
import { QueryParameter, ExecutionState, ExecutionAPI } from "../../src/";
import log from "loglevel";
import { ExecutionPerformance } from "../../src/types/requestPayload";
import { apiKey, expectAsyncThrow } from "./util";
import { BASIC_KEY, expectAsyncThrow } from "./util";

log.setLevel("silent", true);

describe("ExecutionAPI: native routes", () => {
let client: ExecutionAPI;

beforeEach(() => {
client = new ExecutionAPI(BASIC_KEY);
});

// This doesn't work if run too many times at once:
// https://discord.com/channels/757637422384283659/1019910980634939433/1026840715701010473
it("returns expected results on sequence execute-cancel-get_status", async () => {
const client = new ExecutionAPI(apiKey);
// Long running query ID.
const queryID = 1229120;
// Execute and check state
Expand Down Expand Up @@ -40,7 +45,6 @@ describe("ExecutionAPI: native routes", () => {
});

it("successfully executes with query parameters", async () => {
const client = new ExecutionAPI(apiKey);
const queryID = 1215383;
const parameters = [
QueryParameter.text("TextField", "Plain Text"),
Expand All @@ -56,15 +60,13 @@ describe("ExecutionAPI: native routes", () => {
});

it("execute with Large tier performance", async () => {
const client = new ExecutionAPI(apiKey);
const execution = await client.executeQuery(1215383, {
performance: ExecutionPerformance.Large,
});
expect(execution.execution_id).is.not.null;
});

it("returns expected results on cancelled query exection", async () => {
const client = new ExecutionAPI(apiKey);
// Execute and check state
const cancelledExecutionId = "01GEHEC1W8P1V5ENF66R2WY54V";
const result = await client.getExecutionResults(cancelledExecutionId);
Expand All @@ -87,25 +89,31 @@ describe("ExecutionAPI: Errors", () => {
// TODO these errors can't be reached because post method is private
// {"error":"unknown parameters (undefined)"}
// {"error":"Invalid request body payload"}
let client: ExecutionAPI;

beforeEach(() => {
client = new ExecutionAPI(BASIC_KEY);
});

beforeEach(function () {
const client = new ExecutionAPI(BASIC_KEY);
});

it("returns invalid API key", async () => {
const client = new ExecutionAPI("Bad Key");
await expectAsyncThrow(client.executeQuery(1), "invalid API Key");
const bad_client = new ExecutionAPI("Bad Key");
await expectAsyncThrow(bad_client.executeQuery(1), "invalid API Key");
});
it("returns Invalid request path (queryId too large)", async () => {
const client = new ExecutionAPI(apiKey);
await expectAsyncThrow(
client.executeQuery(99999999999999999999999999),
"Invalid request path",
);
});
it("returns query not found error", async () => {
const client = new ExecutionAPI(apiKey);
await expectAsyncThrow(client.executeQuery(999999999), "Query not found");
await expectAsyncThrow(client.executeQuery(0), "Query not found");
});
it("returns invalid job id", async () => {
const client = new ExecutionAPI(apiKey);
await expectAsyncThrow(client.executeQuery(999999999), "Query not found");

const invalidJobID = "Wonky Job ID";
Expand All @@ -118,7 +126,6 @@ describe("ExecutionAPI: Errors", () => {
await expectAsyncThrow(client.cancelExecution(invalidJobID), expectedErrorMessage);
});
it("fails execute with unknown query parameter", async () => {
const client = new ExecutionAPI(apiKey);
const queryID = 1215383;
const invalidParameterName = "Invalid Parameter Name";
await expectAsyncThrow(
Expand All @@ -129,11 +136,9 @@ describe("ExecutionAPI: Errors", () => {
);
});
it("does not allow to execute private queries for other accounts.", async () => {
const client = new ExecutionAPI(apiKey);
await expectAsyncThrow(client.executeQuery(1348384), "Query not found");
});
it("fails with unhandled FAILED_TYPE_UNSPECIFIED when query won't compile", async () => {
const client = new ExecutionAPI(apiKey);
// Execute and check state
// V1 query: 1348966
await expectAsyncThrow(
Expand Down
35 changes: 29 additions & 6 deletions tests/e2e/queryAPI.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import { expect } from "chai";
import { QueryParameter, DuneError, QueryAPI } from "../../src/";
import { apiKey } from "./util";
import { QueryParameter, QueryAPI } from "../../src/";
import { PLUS_KEY, BASIC_KEY, expectAsyncThrow } from "./util";

const PREMIUM_PLAN_MESSAGE =
"CRUD queries is an advanced feature included only in our premium subscription plans. Please upgrade your plan to use it.";

describe("QueryAPI: Premium - CRUD Operations", () => {
let plusClient: QueryAPI;

beforeEach(() => {
plusClient = new QueryAPI(PLUS_KEY);
});

it("create, get & update", async () => {
const client = new QueryAPI(apiKey);
let newQuery = await client.createQuery(
let newQuery = await plusClient.createQuery(
"Name",
"select 1",
[QueryParameter.text("What", "name")],
true,
);
let recoveredQuery = await client.getQuery(newQuery.query_id);
let recoveredQuery = await plusClient.getQuery(newQuery.query_id);
expect(newQuery.query_id).to.be.equal(recoveredQuery.query_id);
let updatedQueryId = await client.updateQuery(
let updatedQueryId = await plusClient.updateQuery(
newQuery.query_id,
"New Name",
"select 10;",
);
expect(updatedQueryId).to.be.equal(recoveredQuery.query_id);
});
});

describe("QueryAPI: Errors", () => {
let basicClient: QueryAPI;

beforeEach(() => {
basicClient = new QueryAPI(BASIC_KEY);
});

it("Basic Plan Failure", async () => {
await expectAsyncThrow(
basicClient.createQuery("Query Name", "select 1"),
PREMIUM_PLAN_MESSAGE,
);
});
});
12 changes: 10 additions & 2 deletions tests/e2e/util.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { expect } from "chai";
import { DuneError } from "../../src";

const { DUNE_API_KEY } = process.env;
export const apiKey: string = DUNE_API_KEY ? DUNE_API_KEY : "No API Key";
const { BASIC_API_KEY, PLUS_API_KEY } = process.env;
if (BASIC_API_KEY === undefined) {
throw Error("Missing ENV var: BASIC_API_KEY");
}
if (PLUS_API_KEY === undefined) {
throw Error("Missing ENV var: PLUS_API_KEY");
}
export const BASIC_KEY: string = BASIC_API_KEY!;
export const PLUS_KEY: string = PLUS_API_KEY!;


export const expectAsyncThrow = async (
promise: Promise<any>,
Expand Down

0 comments on commit a6dc8de

Please sign in to comment.