Skip to content

Commit

Permalink
Feature/183 manage scenario state (#204)
Browse files Browse the repository at this point in the history
* Added page for scenarios

* Implemented scenarios page

* Added scenario form

* Added "Clear all scenarios" button

* Added delete scenario button and made some scenario overview fixes

* Added "Set scenario" button at stub

* Updated changelog

* Updated docs

Co-authored-by: Duco <[email protected]>
  • Loading branch information
dukeofharen and Duco authored Dec 12, 2021
1 parent 9ee06c4 commit d0b9938
Show file tree
Hide file tree
Showing 21 changed files with 349 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[1.1.0]
- Updated HttPlaceholder to .NET 6 (https://github.com/dukeofharen/httplaceholder/issues/202).
- Added possibility to manage scenarios through the user interface.

# BREAKING CHANGES
- Since HttPlaceholder is now using .NET 6, you also need to install .NET 6 SDK on your PC if you use the .NET tool version. If you use the OS specific version of HttPlaceholder, you do not have to have the .NET SDK installed.
Expand Down
32 changes: 32 additions & 0 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
- [Stubs page](#stubs-page)
- [Import stubs](#import-stubs)
- [Add stub(s) form](#add-stubs-form)
- [Scenarios](#scenarios-page)
- [Settings page](#settings-page)
- [Tools and client libraries](#tools-and-client-libraries)
- [HttPlaceholder REST API client for .NET](#httplaceholder-rest-api-client-for-net)
Expand Down Expand Up @@ -2017,6 +2018,7 @@ When clicking on a stub, the stub YAML will be opened and you have a few more st
- Duplicate: copies the contents of the stub and opens them in a new form so you can create a new stub based on this stub.
- Update: opens a form to update this stub.
- Disable / enable: disables or enables this stub.
- Set scenario: opens a form where you can update the variables of the scenario that is linked to this stub. Only available if a scenario has been set on the stub. For more information, read [request scenario](#request-scenario) or [scenario](#scenario).
- Delete: deletes this stub.

## Import stubs
Expand Down Expand Up @@ -2058,9 +2060,11 @@ Chrome also allows you to copy cURL commands for all requests in the network tab
When copying cURL requests from a browser on Windows, make sure you select "Copy as cURL (bash)" or "Copy all as cURL (bash)" on Chrome or "Copy as cURL (POSIX)" in Firefox. The Windows formatting of cURL commands is currently not supported in HttPlaceholder.

![](img/ui/curl_copy_firefox_windows.png)

_Copy as cURL in Firefox on Windows_

![](img/ui/curl_copy_chrome_windows.png)

_Copy as cURL in Chrome on Windows_

## Add stub(s) form
Expand All @@ -2081,6 +2085,34 @@ You can also click on "Add request / response value" so you can see what kind of

Besides this, you can also choose between using the "Advanced editor" (based on [CodeMirror](https://codemirror.net/)) and "Simple editor". A simple editor was added because CodeMirror is not really suited for updating large stubs.

## Scenarios page

For more information about scenarios, go to [request scenario](#request-scenario) or [scenario](#scenario). In short, this page and form allows you to view the scenarios that are currently in use and add new scenarios and update existing ones.

![](img/ui/scenario_overview.png)

You have several options here.

- Add scenario: opens a form where you can add a new scenario.
- Clear all scenarios: like the name says, deletes all the existing scenarios.

Per scenario, you also have a few options:

- Update: opens a form to update this specific scenario.
- Delete: deletes this specific scenario.

### Scenario form

![](img/ui/scenario_form_empty.png)

On this form, you can add a new scenario or update an existing scenario. You have a few fields here.

- Scenario: the scenario name.
- State: the state the scenario is currently in (just a piece of text).
- Hit count: the number of hits a stub under this scenario has been hit.

![](img/ui/scenario_form_filled.png)

## Settings page

On the settings page you can configure all kinds of settings for HttPlaceholder (only frontend settings for now).
Expand Down
Binary file modified docs/img/ui/curl_copy_firefox_windows.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/requests_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/ui/scenario_form_empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/ui/scenario_form_filled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/ui/scenario_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stub_add_yaml.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stub_add_yaml_helpers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stub_curl_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stub_curl_form_review.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stub_upload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ui/stubs_details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions gui/src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export default {
routeName: "ImportStubs",
hideWhenAuthEnabledAndNotLoggedIn: true,
},
{
title: "Scenarios",
icon: "card-list",
routeName: "Scenarios",
hideWhenAuthEnabledAndNotLoggedIn: true,
},
{
title: "Settings",
icon: "wrench",
Expand Down
27 changes: 23 additions & 4 deletions gui/src/components/stub/Stub.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
name: 'Requests',
query: { filter: id },
}"
>Requests</router-link
>
>Requests
</router-link>
<button
class="btn btn-success btn-sm me-2"
title="Duplicate this stub"
Expand All @@ -34,8 +34,8 @@
name: 'StubForm',
params: { stubId: id },
}"
>Update</router-link
>
>Update
</router-link>
<button
v-if="!isReadOnly"
class="btn btn-success btn-sm me-2"
Expand All @@ -44,6 +44,12 @@
>
{{ enableDisableText }}
</button>
<router-link
v-if="hasScenario"
class="btn btn-success btn-sm me-2"
:to="{ name: 'ScenarioForm', params: { scenario: scenario } }"
>Set scenario</router-link
>
<button
v-if="!isReadOnly"
class="btn btn-danger btn-sm me-2"
Expand Down Expand Up @@ -120,6 +126,16 @@ export default {
return yaml.dump(fullStub.value.stub);
});
const scenario = computed(() => {
if (!fullStub.value) {
return null;
}
return fullStub.value.stub.scenario;
});
const hasScenario = computed(() => {
return !!scenario.value;
});
const isReadOnly = computed(() =>
fullStub.value ? fullStub.value.metadata.readOnly : true
);
Expand All @@ -138,6 +154,7 @@ export default {
if (!fullStub.value) {
try {
fullStub.value = await store.dispatch("stubs/getStub", getStubId());
console.log(JSON.stringify(fullStub.value));
initHljs();
// Sadly, when doing this without the timeout, it does the slide down incorrect.
Expand Down Expand Up @@ -195,6 +212,8 @@ export default {
enabled,
accordionOpened,
codeBlock,
hasScenario,
scenario,
};
},
};
Expand Down
3 changes: 3 additions & 0 deletions gui/src/constants/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export const resources = {
filteredStubsDeletedSuccessfully: "Stubs were deleted successfully.",
stubNotFound: "Stub with ID {0} was not found.",
stubsInFileAddedSuccessfully: "Stubs in file '{0}' were added successfully.",
scenarioSetSuccessfully: "The scenario values were set successfully.",
scenariosDeletedSuccessfully: "The scenarios were deleted successfully.",
scenarioDeletedSuccessfully: "The scenario was deleted successfully.",
noCurlStubsFound:
"No stubs could be determined from the cURL command(s). This might mean that you did not provide valid input.",
errorDuringParsingOfYaml: "Something went wrong while parsing the YAML: {0}",
Expand Down
14 changes: 14 additions & 0 deletions gui/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ const routes = [
component: () =>
import(/* webpackChunkName: "login" */ "../views/Login.vue"),
},
{
path: "/scenarios",
name: "Scenarios",
component: () =>
import(/* webpackChunkName: "scenarios" */ "../views/Scenarios.vue"),
},
{
path: "/scenarioForm/:scenario?",
name: "ScenarioForm",
component: () =>
import(
/* webpackChunkName: "scenarioForm" */ "../views/ScenarioForm.vue"
),
},
];

const router = createRouter({
Expand Down
22 changes: 21 additions & 1 deletion gui/src/store/modules/scenarios.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get } from "@/utils/api";
import { get, put, del } from "@/utils/api";

const state = () => ({});

Expand All @@ -8,6 +8,26 @@ const actions = {
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
},
getScenario(_, scenario) {
return get(`/ph-api/scenarios/${scenario}`)
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
},
setScenario(_, scenario) {
return put(`/ph-api/scenarios/${scenario.scenario}`, scenario)
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
},
deleteAllScenarios() {
return del("/ph-api/scenarios")
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
},
deleteScenario(_, scenario) {
return del(`/ph-api/scenarios/${scenario}`)
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
},
};

const mutations = {};
Expand Down
125 changes: 125 additions & 0 deletions gui/src/views/ScenarioForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<template>
<h1>{{ title }}</h1>

<div class="row">
<div class="col-md-12 mb-2">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="Scenario name (required)"
v-model="scenarioForm.scenario"
/>
</div>
</div>
<div class="col-md-12 mb-2">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="Scenario state (optional)"
v-model="scenarioForm.state"
/>
</div>
</div>
<div class="col-md-12 mb-2">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="Scenario hit count (optional)"
v-model="scenarioForm.hitCount"
/>
</div>
</div>
<div class="col-md-12 mb-2">
<button class="btn btn-success" @click="save" :disabled="saveDisabled">
Save
</button>
</div>
</div>
</template>

<script>
import { useRoute, useRouter } from "vue-router";
import { computed, onMounted, onUnmounted, ref } from "vue";
import { useStore } from "vuex";
import { handleHttpError } from "@/utils/error";
import toastr from "toastr";
import { resources } from "@/constants/resources";
import { shouldSave } from "@/utils/event";
export default {
name: "ScenarioForm",
setup() {
const route = useRoute();
const router = useRouter();
const store = useStore();
// Data
const scenarioForm = ref({
scenario: "",
state: "",
hitCount: "",
});
// Computed
const scenarioName = computed(() => route.params.scenario);
const newScenario = computed(() => !scenarioName.value);
const title = computed(() =>
newScenario.value ? "Add scenario" : "Update scenario"
);
const saveDisabled = computed(() => !scenarioForm.value.scenario);
// Methods
const save = async () => {
try {
if (!scenarioForm.value.hitCount) {
scenarioForm.value.hitCount = 0;
}
await store.dispatch("scenarios/setScenario", scenarioForm.value);
toastr.success(resources.scenarioSetSuccessfully);
await router.push({ name: "Scenarios" });
} catch (e) {
handleHttpError(e);
}
};
const checkSave = async (e) => {
if (shouldSave(e) && !saveDisabled.value) {
e.preventDefault();
await save();
}
};
// Lifecycle
const keydownEventListener = async (e) => await checkSave(e);
onMounted(async () => {
document.addEventListener("keydown", keydownEventListener);
if (scenarioName.value) {
scenarioForm.value.scenario = scenarioName.value;
}
if (!newScenario.value) {
try {
scenarioForm.value = await store.dispatch(
"scenarios/getScenario",
scenarioName.value
);
} catch (e) {
if (e.status !== 404) {
handleHttpError(e);
}
}
}
});
onUnmounted(() =>
document.removeEventListener("keydown", keydownEventListener)
);
return { title, scenarioForm, save, saveDisabled };
},
};
</script>

<style scoped></style>
Loading

0 comments on commit d0b9938

Please sign in to comment.