Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions packages/cli/src/commands/explore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,13 +459,31 @@ type DatasetConfig = {
}>;
};

/**
* Translate `--environment` values into a query filter term. A single value
* becomes `environment:foo`; multiple values use the `environment:[a,b]` list
* syntax so they are ORed rather than ANDed.
*/
function buildEnvironmentQuery(
environment: string[] | undefined
): string | undefined {
if (!environment || environment.length === 0) {
return;
}
if (environment.length === 1) {
return `environment:${environment[0]}`;
}
return `environment:[${environment.join(",")}]`;
}

/**
* Resolve dataset-specific configuration: sort, query, validation, and fetch.
*
* For the `replays` dataset this validates fields, resolves replay-specific
* sort, and returns a fetch function that calls `listReplays`. For all other
* datasets it validates environment usage, resolves explore sort (spans-only),
* prepends `project:<slug>` to the query, and returns a `queryEvents` fetch.
* datasets it translates `--environment` values into `environment:...` query
* filter terms, resolves explore sort (spans-only), prepends `project:<slug>`
* to the query, and returns a `queryEvents` fetch.
*/
function resolveDatasetConfig(params: {
dataset: string;
Expand Down Expand Up @@ -518,13 +536,11 @@ function resolveDatasetConfig(params: {
};
}

// Non-replay datasets
if (environment) {
throw new ValidationError(
"--environment is only supported with --dataset replays. Use environment:... inside --query for other datasets.",
"environment"
);
}
// Non-replay datasets: translate --environment into query filter terms
// since the Discover/Events API expects environment:... in the query string.
const envPrefix = buildEnvironmentQuery(environment);
const queryWithEnv =
[envPrefix, flags.query].filter(Boolean).join(" ") || undefined;

const firstAgg = findFirstAggregate(fieldList);
const rawSort = flags.sort ?? (firstAgg ? `-${firstAgg}` : undefined);
Expand All @@ -541,7 +557,7 @@ function resolveDatasetConfig(params: {
sort = undefined;
}

const query = buildProjectQuery(flags.query, project);
const query = buildProjectQuery(queryWithEnv, project);
return {
sort,
query,
Expand Down
45 changes: 37 additions & 8 deletions packages/cli/test/commands/explore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,17 +886,46 @@ describe("sentry explore", () => {
});

describe("validation", () => {
test("rejects --environment on non-replay datasets", async () => {
test("translates --environment into query filter terms on non-replay datasets", async () => {
resolveTargetSpy.mockResolvedValue({ org: "test-org" });
queryEventsSpy.mockResolvedValue({
data: MOCK_EVENTS_RESPONSE,
nextCursor: undefined,
});
const { context } = createContext();

await expect(
func.call(
context,
{ ...DEFAULT_FLAGS, environment: ["production"] },
"test-org/"
)
).rejects.toThrow(ValidationError);
await func.call(
context,
{ ...DEFAULT_FLAGS, environment: ["production"] },
"test-org/"
);

expect(queryEventsSpy).toHaveBeenCalledWith(
"test-org",
expect.objectContaining({ query: "environment:production" })
);
});

test("translates multiple --environment values into environment:[...] syntax", async () => {
resolveTargetSpy.mockResolvedValue({ org: "test-org" });
queryEventsSpy.mockResolvedValue({
data: MOCK_EVENTS_RESPONSE,
nextCursor: undefined,
});
const { context } = createContext();

await func.call(
context,
{ ...DEFAULT_FLAGS, environment: ["production", "canary"] },
"test-org/"
);

expect(queryEventsSpy).toHaveBeenCalledWith(
"test-org",
expect.objectContaining({
query: "environment:[production,canary]",
})
);
});

test("rejects replay detail-only fields on the replay dataset", async () => {
Expand Down
Loading