Initial environment state system refactor

This commit is contained in:
Andrew Bastin
2021-06-04 22:41:07 -04:00
parent c3f713c0bd
commit 5bfeb541fc
11 changed files with 430 additions and 226 deletions

View File

@@ -328,5 +328,4 @@ $responsiveWidth: 768px;
animation: slideIn 0.2s forwards ease-in-out;
}
</style>

View File

@@ -36,48 +36,26 @@
</SmartModal>
</template>
<script>
import { fb } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
<script lang="ts">
import Vue from "vue"
import { createEnvironment } from "~/newstore/environments"
export default {
export default Vue.extend({
props: {
show: Boolean,
},
data() {
return {
name: null,
}
},
subscriptions() {
return {
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
name: null as string | null,
}
},
methods: {
syncEnvironments() {
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
)
}
},
addNewEnvironment() {
if (!this.$data.name) {
this.$toast.info(this.$t("invalid_environment_name"))
if (!this.name) {
this.$toast.info(this.$t("invalid_environment_name").toString())
return
}
const newEnvironment = [
{
name: this.$data.name,
variables: [],
},
]
this.$store.commit("postwoman/importAddEnvironments", {
environments: newEnvironment,
confirmation: "Environment added",
})
this.syncEnvironments()
createEnvironment(this.name)
this.hideModal()
},
hideModal() {
@@ -85,5 +63,5 @@ export default {
this.$emit("hide-modal")
},
},
}
})
</script>

View File

@@ -32,7 +32,7 @@
</div>
</div>
<ul
v-for="(variable, index) in editingEnvCopy.variables"
v-for="(variable, index) in vars"
:key="index"
class="
border-b border-dashed
@@ -46,33 +46,16 @@
>
<li>
<input
v-model="variable.key"
:placeholder="$t('variable_count', { count: index + 1 })"
:name="'param' + index"
:value="variable.key"
autofocus
@change="
$store.commit('postwoman/setVariableKey', {
index,
value: $event.target.value,
})
"
/>
</li>
<li>
<input
v-model="variable.value"
:placeholder="$t('value_count', { count: index + 1 })"
:name="'value' + index"
:value="
typeof variable.value === 'string'
? variable.value
: JSON.stringify(variable.value)
"
@change="
$store.commit('postwoman/setVariableValue', {
index,
value: $event.target.value,
})
"
/>
</li>
<div>
@@ -113,60 +96,44 @@
</SmartModal>
</template>
<script>
import { fb } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
<script lang="ts">
import Vue, { PropType } from "vue"
import clone from "lodash/clone"
import type { Environment } from "~/newstore/environments"
import { updateEnvironment } from "~/newstore/environments"
export default {
export default Vue.extend({
props: {
show: Boolean,
editingEnvironment: { type: Object, default: () => {} },
editingEnvironment: {
type: Object as PropType<Environment | null>,
default: null,
},
editingEnvironmentIndex: { type: Number, default: null },
},
data() {
return {
name: null,
name: null as string | null,
vars: [] as { key: string; value: string }[],
doneButton: '<i class="material-icons">done</i>',
}
},
subscriptions() {
return {
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
}
},
computed: {
editingEnvCopy() {
return this.$store.state.postwoman.editingEnvironment
},
variableString() {
const result = this.editingEnvCopy.variables
return result === "" ? "" : JSON.stringify(result)
},
},
watch: {
editingEnvironment() {
this.name =
this.$props.editingEnvironment && this.$props.editingEnvironment.name
? this.$props.editingEnvironment.name
: undefined
this.$store.commit(
"postwoman/setEditingEnvironment",
this.$props.editingEnvironment
)
this.name = this.editingEnvironment?.name ?? null
this.vars = clone(this.editingEnvironment?.variables ?? [])
},
show() {
this.name = this.editingEnvironment?.name ?? null
this.vars = clone(this.editingEnvironment?.variables ?? [])
},
},
methods: {
syncEnvironments() {
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
)
}
},
clearContent({ target }) {
this.$store.commit("postwoman/removeVariables", [])
clearContent({ target }: { target: HTMLElement }) {
this.vars = []
target.innerHTML = this.doneButton
this.$toast.info(this.$t("cleared"), {
this.$toast.info(this.$t("cleared").toString(), {
icon: "clear_all",
})
setTimeout(
@@ -175,44 +142,26 @@ export default {
)
},
addEnvironmentVariable() {
const value = { key: "", value: "" }
this.$store.commit("postwoman/addVariable", value)
this.syncEnvironments()
},
removeEnvironmentVariable(index) {
const variableIndex = index
const oldVariables = this.editingEnvCopy.variables.slice()
const newVariables = this.editingEnvCopy.variables.filter(
(_, index) => variableIndex !== index
)
this.$store.commit("postwoman/removeVariable", newVariables)
this.$toast.error(this.$t("deleted"), {
icon: "delete",
action: {
text: this.$t("undo"),
onClick: (_, toastObject) => {
this.$store.commit("postwoman/removeVariable", oldVariables)
toastObject.remove()
},
},
this.vars.push({
key: "",
value: "",
})
this.syncEnvironments()
},
removeEnvironmentVariable(index: number) {
this.vars.splice(index, 1)
},
saveEnvironment() {
if (!this.$data.name) {
this.$toast.info(this.$t("invalid_environment_name"))
if (!this.name) {
this.$toast.info(this.$t("invalid_environment_name").toString())
return
}
const environmentUpdated = {
...this.editingEnvCopy,
name: this.$data.name,
const environmentUpdated: Environment = {
name: this.name,
variables: this.vars,
}
this.$store.commit("postwoman/saveEnvironment", {
environment: environmentUpdated,
environmentIndex: this.$props.editingEnvironmentIndex,
})
this.syncEnvironments()
updateEnvironment(this.editingEnvironmentIndex, environmentUpdated)
this.hideModal()
},
hideModal() {
@@ -220,5 +169,5 @@ export default {
this.$emit("hide-modal")
},
},
}
})
</script>

View File

@@ -40,11 +40,11 @@
</div>
</template>
<script>
import { fb } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
<script lang="ts">
import Vue from "vue"
import { deleteEnvironment } from "~/newstore/environments"
export default {
export default Vue.extend({
props: {
environment: { type: Object, default: () => {} },
environmentIndex: { type: Number, default: null },
@@ -54,26 +54,13 @@ export default {
confirmRemove: false,
}
},
subscriptions() {
return {
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
}
},
methods: {
syncEnvironments() {
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
)
}
},
removeEnvironment() {
this.$store.commit("postwoman/removeEnvironment", this.environmentIndex)
this.$toast.error(this.$t("deleted"), {
deleteEnvironment(this.environmentIndex)
this.$toast.error(this.$t("deleted").toString(), {
icon: "delete",
})
this.syncEnvironments()
},
},
}
})
</script>

View File

@@ -75,8 +75,11 @@
</template>
<script>
import { fb } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
import {
environments$,
setCurrentEnvironment,
selectedEnvIndex$,
} from "~/newstore/environments"
export default {
data() {
@@ -87,52 +90,17 @@ export default {
editingEnvironment: undefined,
editingEnvironmentIndex: undefined,
selectedEnvironmentIndex: -1,
defaultEnvironment: {
name: "My Environment Variables",
variables: [],
},
}
},
subscriptions() {
return {
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
environments: environments$,
selectedEnvironmentIndex: selectedEnvIndex$,
}
},
computed: {
environments() {
return fb.currentUser !== null
? fb.currentEnvironments
: this.$store.state.postwoman.environments
},
},
watch: {
selectedEnvironmentIndex(val) {
if (val === -1)
this.$emit("use-environment", {
environment: this.defaultEnvironment,
environments: this.environments,
})
else
this.$emit("use-environment", {
environment: this.environments[val],
environments: this.environments,
})
},
environments: {
handler({ length }) {
if (length === 0) {
this.selectedEnvironmentIndex = -1
this.$emit("use-environment", {
environment: this.defaultEnvironment,
environments: this.environments,
})
} else if (this.environments[this.selectedEnvironmentIndex])
this.$emit("use-environment", {
environment: this.environments[this.selectedEnvironmentIndex],
environments: this.environments,
})
else this.selectedEnvironmentIndex = -1
},
setCurrentEnvironment(val)
},
},
mounted() {
@@ -166,19 +134,11 @@ export default {
this.$data.editingEnvironment = environment
this.$data.editingEnvironmentIndex = environmentIndex
this.displayModalEdit(true)
this.syncEnvironments()
},
resetSelectedData() {
this.$data.editingEnvironment = undefined
this.$data.editingEnvironmentIndex = undefined
},
syncEnvironments() {
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
)
}
},
},
}
</script>

View File

@@ -1,9 +1,6 @@
<template>
<div class="flex flex-col">
<label
>{{ $t("color") }}:
{{ capitalized(active) }}</label
>
<label>{{ $t("color") }}: {{ capitalized(active) }}</label>
<div>
<!-- text-blue-400 -->
<!-- text-green-400 -->
@@ -61,7 +58,6 @@ export default {
},
watch: {
active(color) {
localStorage.setItem("THEME_COLOR", color)
},
},
@@ -72,7 +68,7 @@ export default {
this.active = color
},
capitalized(color) {
return `${color.charAt(0).toUpperCase()}${color.slice(1)}`
return `${color.charAt(0).toUpperCase()}${color.slice(1)}`
},
},
}

View File

@@ -16,6 +16,7 @@ import {
graphqlCollectionStore,
setGraphqlCollections,
} from "~/newstore/collections"
import { environments$ } from "~/newstore/environments"
// Initialize Firebase, copied from cloud console
const firebaseConfig = {
@@ -55,6 +56,7 @@ export class FirebaseInstance {
let loadedGraphqlHistory = false
let loadedRESTCollections = false
let loadedGraphqlCollections = false
const loadedEnvironments = false
graphqlCollectionStore.subject$.subscribe(({ state }) => {
if (
@@ -113,7 +115,7 @@ export class FirebaseInstance {
})
settingsStore.dispatches$.subscribe((dispatch) => {
if (this.currentSettings && loadedSettings) {
if (this.currentUser && loadedSettings) {
if (dispatch.dispatcher === "bulkApplySettings") {
Object.keys(dispatch.payload).forEach((key) => {
this.writeSettings(key, dispatch.payload[key])
@@ -127,6 +129,12 @@ export class FirebaseInstance {
}
})
environments$.subscribe((envs) => {
if (this.currentUser && loadedEnvironments) {
this.writeEnvironments(envs)
}
})
this.app.auth().onIdTokenChanged((user) => {
if (user) {
user.getIdToken().then((token) => {

View File

@@ -1,15 +1,23 @@
export default function getEnvironmentVariablesFromScript(script) {
const _variables = {}
import { getCurrentEnvironment } from "~/newstore/environments"
export default function getEnvironmentVariablesFromScript(script: any) {
const _variables: Record<string, string> = {}
const currentEnv = getCurrentEnvironment()
for (const variable of currentEnv.variables) {
_variables[variable.key] = variable.value
}
try {
// the pw object is the proxy by which pre-request scripts can pass variables to the request.
// for security and control purposes, this is the only way a pre-request script should modify variables.
const pw = {
environment: {
set: (key, value) => (_variables[key] = value),
set: (key: string, value: string) => (_variables[key] = value),
},
env: {
set: (key, value) => (_variables[key] = value),
set: (key: string, value: string) => (_variables[key] = value),
},
// globals that the script is allowed to have access to.
}

320
newstore/environments.ts Normal file
View File

@@ -0,0 +1,320 @@
import { pluck } from "rxjs/operators"
import DispatchingStore, {
defineDispatchers,
} from "~/newstore/DispatchingStore"
export type Environment = {
name: string
variables: {
key: string
value: string
}[]
}
const defaultEnvironmentsState = {
environments: [
{
name: "My Environment Variables",
variables: [],
},
] as Environment[],
// Current environment index specifies the index
// -1 means no environments are selected
currentEnvironmentIndex: -1,
}
type EnvironmentStore = typeof defaultEnvironmentsState
const dispatchers = defineDispatchers({
setCurrentEnviromentIndex(
{ environments }: EnvironmentStore,
{ newIndex }: { newIndex: number }
) {
if (newIndex >= environments.length || newIndex <= -2) {
console.log(
`Ignoring possibly invalid current environment index assignment (value: ${newIndex})`
)
return {}
}
return {
currentEnvironmentIndex: newIndex,
}
},
replaceEnvironments(
_: EnvironmentStore,
{ environments }: { environments: Environment[] }
) {
return {
environments,
}
},
createEnvironment(
{ environments }: EnvironmentStore,
{ name }: { name: string }
) {
return {
environments: [
...environments,
{
name,
variables: [],
},
],
}
},
deleteEnvironment(
{ environments, currentEnvironmentIndex }: EnvironmentStore,
{ envIndex }: { envIndex: number }
) {
return {
environments: environments.filter((_, index) => index !== envIndex),
currentEnvironmentIndex:
envIndex === currentEnvironmentIndex ? -1 : currentEnvironmentIndex,
}
},
renameEnvironment(
{ environments }: EnvironmentStore,
{ envIndex, newName }: { envIndex: number; newName: string }
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
name: newName,
}
: env
),
}
},
updateEnvironment(
{ environments }: EnvironmentStore,
{ envIndex, updatedEnv }: { envIndex: number; updatedEnv: Environment }
) {
return {
environments: environments.map((env, index) =>
index === envIndex ? updatedEnv : env
),
}
},
addEnvironmentVariable(
{ environments }: EnvironmentStore,
{ envIndex, key, value }: { envIndex: number; key: string; value: string }
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
variables: [...env.variables, { key, value }],
}
: env
),
}
},
removeEnvironmentVariable(
{ environments }: EnvironmentStore,
{ envIndex, variableIndex }: { envIndex: number; variableIndex: number }
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
variables: env.variables.filter(
(_, vIndex) => vIndex !== variableIndex
),
}
: env
),
}
},
setEnvironmentVariables(
{ environments }: EnvironmentStore,
{
envIndex,
vars,
}: { envIndex: number; vars: { key: string; value: string }[] }
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
variables: vars,
}
: env
),
}
},
updateEnvironmentVariable(
{ environments }: EnvironmentStore,
{
envIndex,
variableIndex,
updatedKey,
updatedValue,
}: {
envIndex: number
variableIndex: number
updatedKey: string
updatedValue: string
}
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
variables: env.variables.map((v, vIndex) =>
vIndex === variableIndex
? { key: updatedKey, value: updatedValue }
: v
),
}
: env
),
}
},
})
export const environmentsStore = new DispatchingStore(
defaultEnvironmentsState,
dispatchers
)
export const environments$ = environmentsStore.subject$.pipe(
pluck("environments")
)
export const selectedEnvIndex$ = environmentsStore.subject$.pipe(
pluck("currentEnvironmentIndex")
)
export function getCurrentEnvironment(): Environment {
if (environmentsStore.value.currentEnvironmentIndex === -1) {
return {
name: "No Environment",
variables: [],
}
}
return environmentsStore.value.environments[
environmentsStore.value.currentEnvironmentIndex
]
}
export function setCurrentEnvironment(newEnvIndex: number) {
environmentsStore.dispatch({
dispatcher: "setCurrentEnviromentIndex",
payload: {
newIndex: newEnvIndex,
},
})
}
export function replaceEnvironments(newEnvironments: any[]) {
environmentsStore.dispatch({
dispatcher: "replaceEnvironments",
payload: {
environments: newEnvironments,
},
})
}
export function createEnvironment(envName: string) {
environmentsStore.dispatch({
dispatcher: "createEnvironment",
payload: {
name: envName,
},
})
}
export function deleteEnvironment(envIndex: number) {
environmentsStore.dispatch({
dispatcher: "deleteEnvironment",
payload: {
envIndex,
},
})
}
export function renameEnvironment(envIndex: number, newName: string) {
environmentsStore.dispatch({
dispatcher: "renameEnvironment",
payload: {
envIndex,
newName,
},
})
}
export function updateEnvironment(envIndex: number, updatedEnv: Environment) {
environmentsStore.dispatch({
dispatcher: "updateEnvironment",
payload: {
envIndex,
updatedEnv,
},
})
}
export function setEnvironmentVariables(
envIndex: number,
vars: { key: string; value: string }[]
) {
environmentsStore.dispatch({
dispatcher: "setEnvironmentVariables",
payload: {
envIndex,
vars,
},
})
}
export function addEnvironmentVariable(
envIndex: number,
{ key, value }: { key: string; value: string }
) {
environmentsStore.dispatch({
dispatcher: "addEnvironmentVariable",
payload: {
envIndex,
key,
value,
},
})
}
export function removeEnvironmentVariable(
envIndex: number,
variableIndex: number
) {
environmentsStore.dispatch({
dispatcher: "removeEnvironmentVariable",
payload: {
envIndex,
variableIndex,
},
})
}
export function updateEnvironmentVariable(
envIndex: number,
variableIndex: number,
{ key, value }: { key: string; value: string }
) {
environmentsStore.dispatch({
dispatcher: "updateEnvironmentVariable",
payload: {
envIndex,
variableIndex,
updatedKey: key,
updatedValue: value,
},
})
}

View File

@@ -14,6 +14,7 @@ import {
setGraphqlCollections,
setRESTCollections,
} from "./collections"
import { replaceEnvironments, environments$ } from "./environments"
function checkAndMigrateOldSettings() {
const vuexData = JSON.parse(window.localStorage.getItem("vuex") || "{}")
@@ -44,6 +45,14 @@ function checkAndMigrateOldSettings() {
delete vuexData.postwoman.collectionsGraphql
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
}
if (vuexData.postwoman && vuexData.postwoman.environments) {
const envs = vuexData.postwoman.environments
window.localStorage.setItem("environments", JSON.stringify(envs))
delete vuexData.postwoman.environments
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
}
}
function setupSettingsPersistence() {
@@ -102,10 +111,23 @@ function setupCollectionsPersistence() {
})
}
function setupEnvironmentsPersistence() {
const environmentsData = JSON.parse(
window.localStorage.getItem("environments") || "[]"
)
replaceEnvironments(environmentsData)
environments$.subscribe((envs) => {
window.localStorage.setItem("environments", JSON.stringify(envs))
})
}
export function setupLocalPersistence() {
checkAndMigrateOldSettings()
setupSettingsPersistence()
setupHistoryPersistence()
setupCollectionsPersistence()
setupEnvironmentsPersistence()
}

View File

@@ -541,7 +541,7 @@
</SmartTab>
<SmartTab :id="'env'" :label="$t('environments')">
<Environments @use-environment="useSelectedEnvironment($event)" />
<Environments />
</SmartTab>
<SmartTab :id="'notes'" :label="$t('notes')">
@@ -1191,29 +1191,6 @@ export default {
},
},
methods: {
useSelectedEnvironment(args) {
let environment = args.environment
let environments = args.environments
let preRequestScriptString = ""
for (let variable of environment.variables) {
preRequestScriptString += `pw.env.set('${variable.key}', '${variable.value}');\n`
}
for (let env of environments) {
if (env.name === environment.name) {
continue
}
if (env.name === "Globals" || env.name === "globals") {
preRequestScriptString += this.useSelectedEnvironment({
environment: env,
environments,
})
}
}
this.preRequestScript = preRequestScriptString
this.showPreRequestScript = true
return preRequestScriptString
},
checkCollections() {
const checkCollectionAvailability =
this.$store.state.postwoman.collections &&