-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AT-1333 Triggering Bisect from the IJ-Perf
- Loading branch information
Showing
6 changed files
with
414 additions
and
1 deletion.
There are no files selected for viewing
37 changes: 37 additions & 0 deletions
37
dashboard/new-dashboard/src/components/common/sideBar/BisectClient.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { ServerConfigurator } from "../dataQuery" | ||
|
||
interface BisectRequest { | ||
targetValue: string | ||
changes: string | ||
direction: string | ||
test: string | ||
metric: string | ||
branch: string | ||
buildType: string | ||
className: string | ||
} | ||
|
||
export class BisectClient { | ||
private readonly serverConfigurator: ServerConfigurator | null | ||
|
||
constructor(serverConfigurator: ServerConfigurator | null) { | ||
this.serverConfigurator = serverConfigurator | ||
} | ||
|
||
async sendBisectRequest(request: BisectRequest): Promise<string> { | ||
const url = `${this.serverConfigurator?.serverUrl}/api/meta/teamcity/startBisect` | ||
const response = await fetch(url, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(request), | ||
}) | ||
|
||
if (!response.ok) { | ||
const errorMessage = await response.text() | ||
throw new Error(`Failed to send bisect request: ${response.statusText} ${errorMessage}`) | ||
} | ||
return response.text() | ||
} | ||
} |
208 changes: 208 additions & 0 deletions
208
dashboard/new-dashboard/src/components/common/sideBar/BisectDialog.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
<template> | ||
<Dialog | ||
v-model:visible="showDialog" | ||
modal | ||
header="Run bisect" | ||
:style="{ width: '40vw' }" | ||
> | ||
<div class="flex flex-col space-y-8 mb-4 mt-4"> | ||
<FloatLabel> | ||
<InputText | ||
id="targetValue" | ||
v-model="targetValue" | ||
:invalid="!isTargetValueValid()" | ||
/> | ||
<label for="targetValue">Target value</label> | ||
</FloatLabel> | ||
<div class="flex"> | ||
<FloatLabel> | ||
<InputText | ||
id="changes" | ||
v-model="firstCommit" | ||
/> | ||
<label for="changes">First commit</label> | ||
</FloatLabel> | ||
<FloatLabel> | ||
<InputText | ||
id="changes" | ||
v-model="lastCommit" | ||
/> | ||
<label for="changes">Last commit</label> | ||
</FloatLabel> | ||
</div> | ||
<FloatLabel> | ||
<Dropdown | ||
id="direction" | ||
v-model="direction" | ||
:options="['IMPROVEMENT', 'DEGRADATION']" | ||
> | ||
<template #value="{ value }"> | ||
<div class="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900"> | ||
{{ value }} | ||
<ChevronDownIcon | ||
class="-mr-1 ml-1 h-5 w-5 flex-shrink-0 text-gray-400 group-hover:text-gray-500" | ||
aria-hidden="true" | ||
/> | ||
</div> | ||
</template> | ||
<template #dropdownicon> | ||
<!-- empty element to avoid ignoring override of slot --> | ||
<span /> | ||
</template> | ||
</Dropdown> | ||
<label for="direction">Direction</label> | ||
</FloatLabel> | ||
<Accordion> | ||
<AccordionTab header="Additional parameters"> | ||
<div class="flex flex-col space-y-8 mb-4 mt-4"> | ||
<FloatLabel> | ||
<InputText | ||
id="test" | ||
v-model="test" | ||
/> | ||
<label for="test">Test name</label> | ||
</FloatLabel> | ||
<FloatLabel> | ||
<InputText | ||
id="metric" | ||
v-model="metric" | ||
/> | ||
<label for="metric">Metric name</label> | ||
</FloatLabel> | ||
<FloatLabel> | ||
<InputText | ||
id="branch" | ||
v-model="branch" | ||
/> | ||
<label for="metric">Branch</label> | ||
</FloatLabel> | ||
<FloatLabel> | ||
<InputText | ||
id="buildType" | ||
v-model="buildType" | ||
/> | ||
<label for="buildType">Build type</label> | ||
</FloatLabel> | ||
|
||
<FloatLabel> | ||
<InputText | ||
id="className" | ||
v-model="className" | ||
/> | ||
<label for="className">Class name</label> | ||
</FloatLabel> | ||
</div> | ||
</AccordionTab> | ||
</Accordion> | ||
</div> | ||
<div | ||
v-if="error" | ||
class="text-red-500 mb-4" | ||
> | ||
{{ error }} | ||
</div> | ||
<!-- Footer buttons --> | ||
<template #footer> | ||
<div class="flex justify-end space-x-2"> | ||
<Button | ||
label="Cancel" | ||
icon="pi pi-times" | ||
severity="secondary" | ||
@click="showDialog = false" | ||
/> | ||
<Button | ||
label="Start" | ||
icon="pi pi-play" | ||
autofocus | ||
:loading="loading" | ||
:disabled="!isTargetValueValid()" | ||
@click="startBisect" | ||
/> | ||
</div> | ||
</template> | ||
</Dialog> | ||
</template> | ||
<script setup lang="ts"> | ||
import { InfoData } from "./InfoSidebar" | ||
import { getTeamcityBuildType } from "../../../util/artifacts" | ||
import { injectOrError } from "../../../shared/injectionKeys" | ||
import { serverConfiguratorKey } from "../../../shared/keys" | ||
import { computedAsync } from "@vueuse/core" | ||
import { Ref, ref } from "vue" | ||
import { calculateChanges } from "../../../util/changes" | ||
import { ChevronDownIcon } from "@heroicons/vue/20/solid/index" | ||
import { BisectClient } from "./BisectClient" | ||
const { data } = defineProps<{ | ||
data: InfoData | ||
}>() | ||
const serverConfigurator = injectOrError(serverConfiguratorKey) | ||
const showDialog = defineModel<boolean>("showDialog") | ||
const metric = ref(data.series[0].metricName ?? "") | ||
const test = ref(data.projectName) | ||
const branch = ref(data.branch) | ||
const isDegradation = data.deltaPrevious?.includes("-") ?? false | ||
const direction = ref(isDegradation ? "DEGRADATION" : "IMPROVEMENT") | ||
const buildType = computedAsync(async () => await getTeamcityBuildType(serverConfigurator.db, serverConfigurator.table, data.buildId), null) | ||
const firstCommit = ref() | ||
const lastCommit = ref() | ||
const changesMerged = await calculateChanges(serverConfigurator.db, data.installerId ?? data.buildId) | ||
const changesUnmerged = changesMerged?.split("%2C") as string[] | null | ||
if (Array.isArray(changesUnmerged)) { | ||
firstCommit.value = changesUnmerged.at(-1) ?? null | ||
lastCommit.value = changesUnmerged[0] ?? null | ||
} | ||
const methodName = data.description.value?.methodName ?? "" | ||
const className = methodName.slice(0, Math.max(0, methodName.lastIndexOf("#"))) | ||
const targetValue: Ref<string | null> = ref(null) | ||
const bisectClient = new BisectClient(serverConfigurator) | ||
const error = ref<string | null>(null) | ||
const loading = ref(false) | ||
function isTargetValueValid() { | ||
const value = Number(targetValue.value) | ||
return targetValue.value !== null && targetValue.value !== "" && Number.isInteger(value) | ||
} | ||
async function startBisect() { | ||
error.value = null | ||
loading.value = true | ||
try { | ||
//todo add validation on all values | ||
const weburl = await bisectClient.sendBisectRequest({ | ||
targetValue: targetValue.value as string, | ||
changes: (firstCommit.value as string) + "^.." + (lastCommit.value as string), | ||
direction: direction.value, | ||
test: test.value, | ||
metric: metric.value, | ||
branch: branch.value as string, | ||
buildType: buildType.value as string, | ||
className, | ||
}) | ||
showDialog.value = false // Close dialog on success | ||
window.open(weburl, "_blank") | ||
} catch (error_) { | ||
error.value = error_ instanceof Error ? error_.message : "An unknown error occurred" | ||
} finally { | ||
loading.value = false | ||
} | ||
} | ||
</script> | ||
|
||
<style scoped> | ||
.p-inputtext { | ||
@apply w-full; | ||
} | ||
.p-float-label { | ||
@apply w-full; | ||
} | ||
label { | ||
@apply text-sm; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package meta | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
) | ||
|
||
type BisectRequest struct { | ||
TargetValue string `json:"targetValue"` | ||
Changes string `json:"changes"` | ||
Direction string `json:"direction"` | ||
Test string `json:"test"` | ||
Metric string `json:"metric"` | ||
Branch string `json:"branch"` | ||
BuildType string `json:"buildType"` | ||
ClassName string `json:"className"` | ||
} | ||
|
||
func CreatePostStartBisect() http.HandlerFunc { | ||
return func(writer http.ResponseWriter, request *http.Request) { | ||
var bisectReq BisectRequest | ||
decoder := json.NewDecoder(request.Body) | ||
defer request.Body.Close() | ||
err := decoder.Decode(&bisectReq) | ||
if err != nil { | ||
http.Error(writer, "Invalid request body: "+err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
weburlPtr, err := teamCityClient.startBuild(request.Context(), "ijplatform_master_BisectChangeset", map[string]string{ | ||
"target.bisect.direction": bisectReq.Direction, | ||
"target.bisected.metric": bisectReq.Metric, | ||
"target.bisected.simple.class": bisectReq.ClassName, | ||
"target.bisected.test": bisectReq.Test, | ||
"target.branch": bisectReq.Branch, | ||
"target.configuration.id": bisectReq.BuildType, | ||
"target.git.commits": bisectReq.Changes, | ||
"target.value.before.changed.point": bisectReq.TargetValue, | ||
}) | ||
|
||
if err != nil { | ||
http.Error(writer, "Failed to start bisect: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
if weburlPtr != nil { | ||
byteSlice := []byte(*weburlPtr) | ||
_, err = writer.Write(byteSlice) | ||
if err != nil { | ||
http.Error(writer, "Failed to write response: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
} else { | ||
http.Error(writer, "TC response doesn't have weburl", http.StatusInternalServerError) | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.