Compare commits
47 Commits
fix/email-
...
feat/coll-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa8c7a9f35 | ||
|
|
56f427740d | ||
|
|
e4eaa2cec9 | ||
|
|
8bb628354e | ||
|
|
b3bef85b87 | ||
|
|
80d54565eb | ||
|
|
9e7f1b58b9 | ||
|
|
b07d0c69cc | ||
|
|
00ff318194 | ||
|
|
b5c1fe4619 | ||
|
|
8e72ab44a6 | ||
|
|
987d1a74d1 | ||
|
|
d5d4e51848 | ||
|
|
509775878a | ||
|
|
95953557de | ||
|
|
9d2b7cc03f | ||
|
|
276d48e3f8 | ||
|
|
6526247869 | ||
|
|
08b6e0b747 | ||
|
|
79a8bc669e | ||
|
|
b6f3b24b9e | ||
|
|
16bbfec736 | ||
|
|
ebe680c596 | ||
|
|
f79ab298da | ||
|
|
83019733e3 | ||
|
|
c18e801420 | ||
|
|
bb8c77fa7a | ||
|
|
951cff9f30 | ||
|
|
574d800a12 | ||
|
|
ed825cf648 | ||
|
|
569c170fec | ||
|
|
056a8a8719 | ||
|
|
9bfe195253 | ||
|
|
fce68de282 | ||
|
|
ad7b3f05b1 | ||
|
|
ea8de655d7 | ||
|
|
369d01a399 | ||
|
|
0a54455fe7 | ||
|
|
b893607ad1 | ||
|
|
7c3a84246d | ||
|
|
2687592a56 | ||
|
|
a48d7e879f | ||
|
|
d326063659 | ||
|
|
59735c15e4 | ||
|
|
ae531f5882 | ||
|
|
f8aeb42da5 | ||
|
|
1896e5afe1 |
@@ -1,8 +1,8 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { HoppCollection } from "@hoppscotch/data";
|
||||
import { HoppEnvs } from "./request";
|
||||
|
||||
export type CollectionRunnerParam = {
|
||||
collections: HoppCollection<HoppRESTRequest>[];
|
||||
collections: HoppCollection[];
|
||||
envs: HoppEnvs;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export type HoppEnvs = {
|
||||
|
||||
export type CollectionStack = {
|
||||
path: string;
|
||||
collection: HoppCollection<HoppRESTRequest>;
|
||||
collection: HoppCollection;
|
||||
};
|
||||
|
||||
export type RequestReport = {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
isHoppRESTRequest,
|
||||
} from "@hoppscotch/data";
|
||||
import { HoppCollection, isHoppRESTRequest } from "@hoppscotch/data";
|
||||
import * as A from "fp-ts/Array";
|
||||
import { CommanderError } from "commander";
|
||||
import { HoppCLIError, HoppErrnoException } from "../types/errors";
|
||||
@@ -24,9 +20,7 @@ export const hasProperty = <P extends PropertyKey>(
|
||||
* @returns True, if unknown parameter is valid Hoppscotch REST Collection;
|
||||
* False, otherwise.
|
||||
*/
|
||||
export const isRESTCollection = (
|
||||
param: unknown
|
||||
): param is HoppCollection<HoppRESTRequest> => {
|
||||
export const isRESTCollection = (param: unknown): param is HoppCollection => {
|
||||
if (!!param && typeof param === "object") {
|
||||
if (!hasProperty(param, "v") || typeof param.v !== "number") {
|
||||
return false;
|
||||
@@ -62,7 +56,6 @@ export const isRESTCollection = (
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if given error data is of type HoppCLIError, based on existence
|
||||
* of code property.
|
||||
|
||||
@@ -3,7 +3,7 @@ import { pipe } from "fp-ts/function";
|
||||
import { bold } from "chalk";
|
||||
import { log } from "console";
|
||||
import round from "lodash/round";
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { HoppCollection } from "@hoppscotch/data";
|
||||
import {
|
||||
HoppEnvs,
|
||||
CollectionStack,
|
||||
@@ -41,58 +41,58 @@ const { WARN, FAIL } = exceptionColors;
|
||||
* @param param Data of hopp-collection with hopp-requests, envs to be processed.
|
||||
* @returns List of report for each processed request.
|
||||
*/
|
||||
export const collectionsRunner =
|
||||
async (param: CollectionRunnerParam): Promise<RequestReport[]> =>
|
||||
{
|
||||
const envs: HoppEnvs = param.envs;
|
||||
const delay = param.delay ?? 0;
|
||||
const requestsReport: RequestReport[] = [];
|
||||
const collectionStack: CollectionStack[] = getCollectionStack(
|
||||
param.collections
|
||||
);
|
||||
export const collectionsRunner = async (
|
||||
param: CollectionRunnerParam
|
||||
): Promise<RequestReport[]> => {
|
||||
const envs: HoppEnvs = param.envs;
|
||||
const delay = param.delay ?? 0;
|
||||
const requestsReport: RequestReport[] = [];
|
||||
const collectionStack: CollectionStack[] = getCollectionStack(
|
||||
param.collections
|
||||
);
|
||||
|
||||
while (collectionStack.length) {
|
||||
// Pop out top-most collection from stack to be processed.
|
||||
const { collection, path } = <CollectionStack>collectionStack.pop();
|
||||
while (collectionStack.length) {
|
||||
// Pop out top-most collection from stack to be processed.
|
||||
const { collection, path } = <CollectionStack>collectionStack.pop();
|
||||
|
||||
// Processing each request in collection
|
||||
for (const request of collection.requests) {
|
||||
const _request = preProcessRequest(request);
|
||||
const requestPath = `${path}/${_request.name}`;
|
||||
const processRequestParams: ProcessRequestParams = {
|
||||
path: requestPath,
|
||||
request: _request,
|
||||
envs,
|
||||
delay,
|
||||
};
|
||||
// Processing each request in collection
|
||||
for (const request of collection.requests) {
|
||||
const _request = preProcessRequest(request);
|
||||
const requestPath = `${path}/${_request.name}`;
|
||||
const processRequestParams: ProcessRequestParams = {
|
||||
path: requestPath,
|
||||
request: _request,
|
||||
envs,
|
||||
delay,
|
||||
};
|
||||
|
||||
// Request processing initiated message.
|
||||
log(WARN(`\nRunning: ${bold(requestPath)}`));
|
||||
// Request processing initiated message.
|
||||
log(WARN(`\nRunning: ${bold(requestPath)}`));
|
||||
|
||||
// Processing current request.
|
||||
const result = await processRequest(processRequestParams)();
|
||||
// Processing current request.
|
||||
const result = await processRequest(processRequestParams)();
|
||||
|
||||
// Updating global & selected envs with new envs from processed-request output.
|
||||
const { global, selected } = result.envs;
|
||||
envs.global = global;
|
||||
envs.selected = selected;
|
||||
// Updating global & selected envs with new envs from processed-request output.
|
||||
const { global, selected } = result.envs;
|
||||
envs.global = global;
|
||||
envs.selected = selected;
|
||||
|
||||
// Storing current request's report.
|
||||
const requestReport = result.report;
|
||||
requestsReport.push(requestReport);
|
||||
}
|
||||
|
||||
// Pushing remaining folders realted collection to stack.
|
||||
for (const folder of collection.folders) {
|
||||
collectionStack.push({
|
||||
path: `${path}/${folder.name}`,
|
||||
collection: folder,
|
||||
});
|
||||
}
|
||||
// Storing current request's report.
|
||||
const requestReport = result.report;
|
||||
requestsReport.push(requestReport);
|
||||
}
|
||||
|
||||
return requestsReport;
|
||||
};
|
||||
// Pushing remaining folders realted collection to stack.
|
||||
for (const folder of collection.folders) {
|
||||
collectionStack.push({
|
||||
path: `${path}/${folder.name}`,
|
||||
collection: folder,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return requestsReport;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms collections to generate collection-stack which describes each collection's
|
||||
@@ -100,9 +100,7 @@ export const collectionsRunner =
|
||||
* @param collections Hopp-collection objects to be mapped to collection-stack type.
|
||||
* @returns Mapped collections to collection-stack.
|
||||
*/
|
||||
const getCollectionStack = (
|
||||
collections: HoppCollection<HoppRESTRequest>[]
|
||||
): CollectionStack[] =>
|
||||
const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] =>
|
||||
pipe(
|
||||
collections,
|
||||
A.map(
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "fs/promises";
|
||||
import { FormDataEntry } from "../types/request";
|
||||
import { error } from "../types/errors";
|
||||
import { isRESTCollection, isHoppErrnoException } from "./checks";
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { HoppCollection } from "@hoppscotch/data";
|
||||
|
||||
/**
|
||||
* Parses array of FormDataEntry to FormData.
|
||||
@@ -35,20 +35,20 @@ export const parseErrorMessage = (e: unknown) => {
|
||||
};
|
||||
|
||||
export async function readJsonFile(path: string): Promise<unknown> {
|
||||
if(!path.endsWith('.json')) {
|
||||
throw error({ code: "INVALID_FILE_TYPE", data: path })
|
||||
if (!path.endsWith(".json")) {
|
||||
throw error({ code: "INVALID_FILE_TYPE", data: path });
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(path)
|
||||
await fs.access(path);
|
||||
} catch (e) {
|
||||
throw error({ code: "FILE_NOT_FOUND", path: path })
|
||||
throw error({ code: "FILE_NOT_FOUND", path: path });
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse((await fs.readFile(path)).toString())
|
||||
} catch(e) {
|
||||
throw error({ code: "UNKNOWN_ERROR", data: e })
|
||||
return JSON.parse((await fs.readFile(path)).toString());
|
||||
} catch (e) {
|
||||
throw error({ code: "UNKNOWN_ERROR", data: e });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,22 +56,24 @@ export async function readJsonFile(path: string): Promise<unknown> {
|
||||
* Parses collection json file for given path:context.path, and validates
|
||||
* the parsed collectiona array.
|
||||
* @param path Collection json file path.
|
||||
* @returns For successful parsing we get array of HoppCollection<HoppRESTRequest>,
|
||||
* @returns For successful parsing we get array of HoppCollection,
|
||||
*/
|
||||
export async function parseCollectionData(
|
||||
path: string
|
||||
): Promise<HoppCollection<HoppRESTRequest>[]> {
|
||||
let contents = await readJsonFile(path)
|
||||
): Promise<HoppCollection[]> {
|
||||
let contents = await readJsonFile(path);
|
||||
|
||||
const maybeArrayOfCollections: unknown[] = Array.isArray(contents) ? contents : [contents]
|
||||
const maybeArrayOfCollections: unknown[] = Array.isArray(contents)
|
||||
? contents
|
||||
: [contents];
|
||||
|
||||
if(maybeArrayOfCollections.some((x) => !isRESTCollection(x))) {
|
||||
if (maybeArrayOfCollections.some((x) => !isRESTCollection(x))) {
|
||||
throw error({
|
||||
code: "MALFORMED_COLLECTION",
|
||||
path,
|
||||
data: "Please check the collection data.",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return maybeArrayOfCollections as HoppCollection<HoppRESTRequest>[]
|
||||
};
|
||||
return maybeArrayOfCollections as HoppCollection[];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
--properties-primary-sticky-fold: 2.05rem;
|
||||
}
|
||||
|
||||
@mixin light-theme {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Prettify",
|
||||
"properties":"Properties",
|
||||
"remove": "Remove",
|
||||
"rename": "Rename",
|
||||
"restore": "Restore",
|
||||
@@ -172,6 +173,8 @@
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "New Collection",
|
||||
"order_changed": "Collection Order Updated",
|
||||
"properties":"Colection Properties",
|
||||
"properties_updated": "Collection Properties Updated",
|
||||
"renamed": "Collection renamed",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Save as",
|
||||
@@ -354,6 +357,8 @@
|
||||
"offline_short": "You're using Hoppscotch offline.",
|
||||
"post_request_tests": "Test scripts are written in JavaScript, and are run after the response is received.",
|
||||
"pre_request_script": "Pre-request scripts are written in JavaScript, and are run before the request is sent.",
|
||||
"collection_properties_authorization": " This authorization will be set for every request in this collection.",
|
||||
"collection_properties_header": "This header will be set for every request in this collection.",
|
||||
"script_fail": "It seems there is a glitch in the pre-request script. Check the error below and fix the script accordingly.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Write a test script to automate debugging."
|
||||
|
||||
@@ -56,6 +56,7 @@ declare module 'vue' {
|
||||
CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default']
|
||||
CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default']
|
||||
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
|
||||
CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
|
||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { computed } from "vue"
|
||||
import { graphqlCollectionStore } from "~/newstore/collections"
|
||||
|
||||
@@ -28,7 +28,7 @@ const pathFolders = computed(() => {
|
||||
.slice(0, -1)
|
||||
.map((x) => parseInt(x))
|
||||
|
||||
const pathItems: HoppCollection<HoppGQLRequest>[] = []
|
||||
const pathItems: HoppCollection[] = []
|
||||
|
||||
let currentFolder =
|
||||
graphqlCollectionStore.value.state[folderIndicies.shift()!]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { computed } from "vue"
|
||||
import { restCollectionStore } from "~/newstore/collections"
|
||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||
@@ -36,7 +36,7 @@ const pathFolders = computed(() => {
|
||||
.slice(0, -1)
|
||||
.map((x) => parseInt(x))
|
||||
|
||||
const pathItems: HoppCollection<HoppRESTRequest>[] = []
|
||||
const pathItems: HoppCollection[] = []
|
||||
|
||||
let currentFolder = restCollectionStore.value.state[folderIndicies.shift()!]
|
||||
pathItems.push(currentFolder)
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
@keyup.e="edit?.$el.click()"
|
||||
@keyup.delete="deleteAction?.$el.click()"
|
||||
@keyup.x="exportAction?.$el.click()"
|
||||
@keyup.p="propertiesAction?.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
@@ -159,6 +160,18 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="propertiesAction"
|
||||
:icon="IconSettings2"
|
||||
:label="t('action.properties')"
|
||||
:shortcut="['P']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-properties')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
@@ -193,8 +206,9 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import IconSettings2 from "~icons/lucide/settings-2"
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||
@@ -213,7 +227,7 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
id: string
|
||||
parentID?: string | null
|
||||
data: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
data: HoppCollection | TeamCollection
|
||||
/**
|
||||
* Collection component can be used for both collections and folders.
|
||||
* folderType is used to determine which one it is.
|
||||
@@ -245,6 +259,7 @@ const emit = defineEmits<{
|
||||
(event: "add-request"): void
|
||||
(event: "add-folder"): void
|
||||
(event: "edit-collection"): void
|
||||
(event: "edit-properties"): void
|
||||
(event: "export-data"): void
|
||||
(event: "remove-collection"): void
|
||||
(event: "drop-event", payload: DataTransfer): void
|
||||
@@ -261,6 +276,7 @@ const edit = ref<HTMLButtonElement | null>(null)
|
||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||
const exportAction = ref<HTMLButtonElement | null>(null)
|
||||
const options = ref<TippyComponent | null>(null)
|
||||
const propertiesAction = ref<TippyComponent | null>(null)
|
||||
|
||||
const dragging = ref(false)
|
||||
const ordering = ref(false)
|
||||
@@ -294,8 +310,8 @@ const collectionIcon = computed(() => {
|
||||
})
|
||||
|
||||
const collectionName = computed(() => {
|
||||
if ((props.data as HoppCollection<HoppRESTRequest>).name)
|
||||
return (props.data as HoppCollection<HoppRESTRequest>).name
|
||||
if ((props.data as HoppCollection).name)
|
||||
return (props.data as HoppCollection).name
|
||||
return (props.data as TeamCollection).title
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import { PropType, computed, ref } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
||||
import MyCollectionImport from "~/components/importExport/ImportExportSteps/MyCollectionImport.vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
@@ -88,9 +87,7 @@ const showImportFailedError = () => {
|
||||
toast.error(t("import.failed"))
|
||||
}
|
||||
|
||||
const handleImportToStore = async (
|
||||
collections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
const handleImportToStore = async (collections: HoppCollection[]) => {
|
||||
const importResult =
|
||||
props.collectionsType.type === "my-collections"
|
||||
? await importToPersonalWorkspace(collections)
|
||||
@@ -104,26 +101,47 @@ const handleImportToStore = async (
|
||||
}
|
||||
}
|
||||
|
||||
const importToPersonalWorkspace = (
|
||||
collections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
const importToPersonalWorkspace = (collections: HoppCollection[]) => {
|
||||
appendRESTCollections(collections)
|
||||
return E.right({
|
||||
success: true,
|
||||
})
|
||||
}
|
||||
|
||||
const importToTeamsWorkspace = async (
|
||||
collections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
function translateToTeamCollectionFormat(x: HoppCollection) {
|
||||
const folders: HoppCollection[] = (x.folders ?? []).map(
|
||||
translateToTeamCollectionFormat
|
||||
)
|
||||
|
||||
const data = {
|
||||
auth: x.auth,
|
||||
headers: x.headers,
|
||||
}
|
||||
|
||||
const obj = {
|
||||
...x,
|
||||
folders,
|
||||
data,
|
||||
}
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const importToTeamsWorkspace = async (collections: HoppCollection[]) => {
|
||||
if (!hasTeamWriteAccess.value || !selectedTeamID.value) {
|
||||
return E.left({
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
|
||||
const transformedCollection = collections.map((collection) =>
|
||||
translateToTeamCollectionFormat(collection)
|
||||
)
|
||||
|
||||
const res = await toTeamsImporter(
|
||||
JSON.stringify(collections),
|
||||
JSON.stringify(transformedCollection),
|
||||
selectedTeamID.value
|
||||
)()
|
||||
|
||||
@@ -407,7 +425,6 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
||||
},
|
||||
action: async () => {
|
||||
isHoppTeamCollectionExporterInProgress.value = true
|
||||
|
||||
if (
|
||||
props.collectionsType.type === "team-collections" &&
|
||||
props.collectionsType.selectedTeam
|
||||
|
||||
@@ -71,6 +71,13 @@
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@edit-properties="
|
||||
node.data.type === 'collections' &&
|
||||
emit('edit-properties', {
|
||||
collectionIndex: node.id,
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@export-data="
|
||||
node.data.type === 'collections' &&
|
||||
emit('export-data', node.data.data.data)
|
||||
@@ -139,6 +146,13 @@
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@edit-properties="
|
||||
node.data.type === 'folders' &&
|
||||
emit('edit-properties', {
|
||||
collectionIndex: node.id,
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@export-data="
|
||||
node.data.type === 'folders' &&
|
||||
emit('export-data', node.data.data.data)
|
||||
@@ -344,7 +358,7 @@ export type Collection = {
|
||||
isLastItem: boolean
|
||||
data: {
|
||||
parentIndex: null
|
||||
data: HoppCollection<HoppRESTRequest>
|
||||
data: HoppCollection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +367,7 @@ type Folder = {
|
||||
isLastItem: boolean
|
||||
data: {
|
||||
parentIndex: string
|
||||
data: HoppCollection<HoppRESTRequest>
|
||||
data: HoppCollection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +394,7 @@ type CollectionType =
|
||||
|
||||
const props = defineProps({
|
||||
filteredCollections: {
|
||||
type: Array as PropType<HoppCollection<HoppRESTRequest>[]>,
|
||||
type: Array as PropType<HoppCollection[]>,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
@@ -412,28 +426,35 @@ const emit = defineEmits<{
|
||||
event: "add-request",
|
||||
payload: {
|
||||
path: string
|
||||
folder: HoppCollection<HoppRESTRequest>
|
||||
folder: HoppCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "add-folder",
|
||||
payload: {
|
||||
path: string
|
||||
folder: HoppCollection<HoppRESTRequest>
|
||||
folder: HoppCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "edit-collection",
|
||||
payload: {
|
||||
collectionIndex: string
|
||||
collection: HoppCollection<HoppRESTRequest>
|
||||
collection: HoppCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "edit-folder",
|
||||
payload: {
|
||||
folderPath: string
|
||||
folder: HoppCollection<HoppRESTRequest>
|
||||
folder: HoppCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "edit-properties",
|
||||
payload: {
|
||||
collectionIndex: string
|
||||
collection: HoppCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
@@ -451,7 +472,7 @@ const emit = defineEmits<{
|
||||
request: HoppRESTRequest
|
||||
}
|
||||
): void
|
||||
(event: "export-data", payload: HoppCollection<HoppRESTRequest>): void
|
||||
(event: "export-data", payload: HoppCollection): void
|
||||
(event: "remove-collection", payload: string): void
|
||||
(event: "remove-folder", payload: string): void
|
||||
(
|
||||
@@ -665,10 +686,10 @@ const updateCollectionOrder = (
|
||||
type MyCollectionNode = Collection | Folder | Requests
|
||||
|
||||
class MyCollectionsAdapter implements SmartTreeAdapter<MyCollectionNode> {
|
||||
constructor(public data: Ref<HoppCollection<HoppRESTRequest>[]>) {}
|
||||
constructor(public data: Ref<HoppCollection[]>) {}
|
||||
|
||||
navigateToFolderWithIndexPath(
|
||||
collections: HoppCollection<HoppRESTRequest>[],
|
||||
collections: HoppCollection[],
|
||||
indexPaths: number[]
|
||||
) {
|
||||
if (indexPaths.length === 0) return null
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('collection.properties')"
|
||||
:full-width-body="true"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedOptionTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab :id="'headers'" :label="`${t('tab.headers')}`">
|
||||
<HttpHeaders
|
||||
v-model="editableCollection"
|
||||
:is-collection-property="true"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
<div class="bg-bannerInfo p-2 flex items-center">
|
||||
<icon-lucide-info class="svg-icons mr-2" />
|
||||
{{ t("helpers.collection_properties_header") }}
|
||||
<a href="hopp.sh" target="_blank" class="underline">{{
|
||||
t("action.learn_more")
|
||||
}}</a>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'authorization'"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization
|
||||
v-model="editableCollection.auth"
|
||||
:is-collection-property="true"
|
||||
:is-root-collection="editingProperties?.isRootCollection"
|
||||
:inherited-properties="editingProperties?.inheritedProperties"
|
||||
/>
|
||||
<div class="bg-bannerInfo p-2 flex items-center">
|
||||
<icon-lucide-info class="svg-icons mr-2" />
|
||||
{{ t("helpers.collection_properties_authorization") }}
|
||||
<a href="hopp.sh" target="_blank" class="underline">{{
|
||||
t("action.learn_more")
|
||||
}}</a>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex space-x-2">
|
||||
<HoppButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
outline
|
||||
@click="saveEditedCollection"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||
import { clone } from "lodash-es"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type EditingProperties = {
|
||||
collection: HoppCollection | TeamCollection | null
|
||||
isRootCollection: boolean
|
||||
path: string
|
||||
inheritedProperties: HoppInheritedProperty | undefined
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
show: boolean
|
||||
loadingState: boolean
|
||||
editingProperties: EditingProperties | null
|
||||
}>(),
|
||||
{
|
||||
show: false,
|
||||
loadingState: false,
|
||||
editingProperties: null,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "set-collection-properties", newCollection: any): void
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const editableCollection = ref({
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
}) as any
|
||||
|
||||
const selectedOptionTab = ref("headers")
|
||||
|
||||
const changeOptionTab = (tab: RESTOptionTabs) => {
|
||||
selectedOptionTab.value = tab
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show && props.editingProperties?.collection) {
|
||||
editableCollection.value.auth = clone(
|
||||
props.editingProperties.collection.auth
|
||||
)
|
||||
editableCollection.value.headers = clone(
|
||||
props.editingProperties.collection.headers
|
||||
)
|
||||
} else {
|
||||
editableCollection.value = {
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const saveEditedCollection = () => {
|
||||
if (!props.editingProperties) return
|
||||
const finalCollection = clone(editableCollection.value)
|
||||
delete finalCollection.body
|
||||
const collection = {
|
||||
path: props.editingProperties.path,
|
||||
collection: {
|
||||
...props.editingProperties.collection,
|
||||
...finalCollection,
|
||||
},
|
||||
isRootCollection: props.editingProperties.isRootCollection,
|
||||
}
|
||||
emit("set-collection-properties", collection)
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
@@ -74,6 +74,7 @@ import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import {
|
||||
cascadeParentCollectionForHeaderAuth,
|
||||
editGraphqlRequest,
|
||||
editRESTRequest,
|
||||
saveGraphqlRequestAs,
|
||||
@@ -239,6 +240,16 @@ const saveRequestAs = async () => {
|
||||
},
|
||||
}
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
`${picked.value.collectionIndex}`,
|
||||
"rest"
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SAVE_REQUEST",
|
||||
createdNow: true,
|
||||
@@ -266,6 +277,16 @@ const saveRequestAs = async () => {
|
||||
},
|
||||
}
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
picked.value.folderPath,
|
||||
"rest"
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SAVE_REQUEST",
|
||||
createdNow: true,
|
||||
@@ -294,6 +315,16 @@ const saveRequestAs = async () => {
|
||||
},
|
||||
}
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
picked.value.folderPath,
|
||||
"rest"
|
||||
)
|
||||
|
||||
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SAVE_REQUEST",
|
||||
createdNow: false,
|
||||
@@ -378,6 +409,16 @@ const saveRequestAs = async () => {
|
||||
workspaceType: "team",
|
||||
})
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
picked.value.folderPath,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "gql-my-folder") {
|
||||
// TODO: Check for GQL request ?
|
||||
@@ -393,6 +434,16 @@ const saveRequestAs = async () => {
|
||||
workspaceType: "team",
|
||||
})
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
picked.value.folderPath,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "gql-my-collection") {
|
||||
// TODO: Check for GQL request ?
|
||||
@@ -408,6 +459,16 @@ const saveRequestAs = async () => {
|
||||
workspaceType: "team",
|
||||
})
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
`${picked.value.collectionIndex}`,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,13 @@
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@edit-properties="
|
||||
node.data.type === 'collections' &&
|
||||
emit('edit-properties', {
|
||||
collectionIndex: node.id,
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@export-data="
|
||||
node.data.type === 'collections' &&
|
||||
emit('export-data', node.data.data.data)
|
||||
@@ -159,6 +166,13 @@
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@edit-properties="
|
||||
node.data.type === 'folders' &&
|
||||
emit('edit-properties', {
|
||||
collectionIndex: node.id,
|
||||
collection: node.data.data.data,
|
||||
})
|
||||
"
|
||||
@export-data="
|
||||
node.data.type === 'folders' &&
|
||||
emit('export-data', node.data.data.data)
|
||||
@@ -238,6 +252,7 @@
|
||||
selectRequest({
|
||||
request: node.data.data.data.request,
|
||||
requestIndex: node.data.data.data.id,
|
||||
folderPath: getPath(node.id),
|
||||
})
|
||||
"
|
||||
@share-request="
|
||||
@@ -452,6 +467,13 @@ const emit = defineEmits<{
|
||||
folder: TeamCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "edit-properties",
|
||||
payload: {
|
||||
collectionIndex: string
|
||||
collection: TeamCollection
|
||||
}
|
||||
): void
|
||||
(
|
||||
event: "edit-request",
|
||||
payload: {
|
||||
@@ -482,7 +504,7 @@ const emit = defineEmits<{
|
||||
request: HoppRESTRequest
|
||||
requestIndex: string
|
||||
isActive: boolean
|
||||
folderPath?: string | undefined
|
||||
folderPath: string
|
||||
}
|
||||
): void
|
||||
(
|
||||
@@ -530,6 +552,12 @@ const emit = defineEmits<{
|
||||
(event: "display-modal-import-export"): void
|
||||
}>()
|
||||
|
||||
const getPath = (path: string) => {
|
||||
const pathArray = path.split("/")
|
||||
pathArray.pop()
|
||||
return pathArray.join("/")
|
||||
}
|
||||
|
||||
const teamCollectionsList = toRef(props, "teamCollectionList")
|
||||
|
||||
const hasNoTeamAccess = computed(
|
||||
@@ -586,6 +614,7 @@ const isActiveRequest = (requestID: string) => {
|
||||
const selectRequest = (data: {
|
||||
request: HoppRESTRequest
|
||||
requestIndex: string
|
||||
folderPath: string | null
|
||||
}) => {
|
||||
const { request, requestIndex } = data
|
||||
if (props.saveRequest) {
|
||||
@@ -598,6 +627,7 @@ const selectRequest = (data: {
|
||||
request: request,
|
||||
requestIndex: requestIndex,
|
||||
isActive: isActiveRequest(requestIndex),
|
||||
folderPath: data.folderPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,58 +32,58 @@
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppGQLRequest, makeCollection } from "@hoppscotch/data"
|
||||
import { makeCollection } from "@hoppscotch/data"
|
||||
import { addGraphqlCollection } from "~/newstore/collections"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
emits: ["hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null as string | null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addNewCollection() {
|
||||
if (!this.name) {
|
||||
this.toast.error(`${this.t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
addGraphqlCollection(
|
||||
makeCollection<HoppGQLRequest>({
|
||||
name: this.name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
})
|
||||
)
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
this.hideModal()
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_CREATE_COLLECTION",
|
||||
isRootCollection: true,
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
const name = ref<string | null>(null)
|
||||
|
||||
const addNewCollection = () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
addGraphqlCollection(
|
||||
makeCollection({
|
||||
name: name.value,
|
||||
folders: [],
|
||||
requests: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
})
|
||||
)
|
||||
|
||||
hideModal()
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_CREATE_COLLECTION",
|
||||
isRootCollection: true,
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
})
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
name.value = null
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('folder.new')"
|
||||
@close="$emit('hide-modal')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<HoppSmartInput
|
||||
@@ -32,47 +32,49 @@
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { defineComponent } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
folderPath: { type: String, default: null },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
},
|
||||
emits: ["hide-modal", "add-folder"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
folderPath?: string
|
||||
collectionIndex: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
(
|
||||
e: "add-folder",
|
||||
v: {
|
||||
name: string
|
||||
path: string | undefined
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addFolder() {
|
||||
if (!this.name) {
|
||||
this.toast.error(`${this.t("folder.name_length_insufficient")}`)
|
||||
return
|
||||
}
|
||||
): void
|
||||
}>()
|
||||
|
||||
this.$emit("add-folder", {
|
||||
name: this.name,
|
||||
path: this.folderPath || `${this.collectionIndex}`,
|
||||
})
|
||||
const name = ref<string | null>(null)
|
||||
|
||||
this.hideModal()
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
const addFolder = () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("folder.name_length_insufficient")}`)
|
||||
return
|
||||
}
|
||||
|
||||
emit("add-folder", {
|
||||
name: name.value,
|
||||
path: props.folderPath || `${props.collectionIndex}`,
|
||||
})
|
||||
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
name.value = null
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -128,6 +128,21 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="propertiesAction"
|
||||
:icon="IconSettings2"
|
||||
:label="t('action.properties')"
|
||||
:shortcut="['P']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-properties', {
|
||||
collectionIndex: String(collectionIndex),
|
||||
collection: collection,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
@@ -155,7 +170,15 @@
|
||||
@edit-folder="$emit('edit-folder', $event)"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@edit-properties="
|
||||
$emit('edit-properties', {
|
||||
collectionIndex: `${collectionIndex}/${String(index)}`,
|
||||
collection: folder,
|
||||
})
|
||||
"
|
||||
@select="$emit('select', $event)"
|
||||
@select-request="$emit('select-request', $event)"
|
||||
@drop-request="$emit('drop-request', $event)"
|
||||
/>
|
||||
<CollectionsGraphqlRequest
|
||||
v-for="(request, index) in collection.requests"
|
||||
@@ -171,6 +194,7 @@
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@select-request="$emit('select-request', $event)"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="
|
||||
@@ -214,25 +238,24 @@ import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconSettings2 from "~icons/lucide/settings-2"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import {
|
||||
removeGraphqlCollection,
|
||||
moveGraphqlRequest,
|
||||
} from "~/newstore/collections"
|
||||
import { removeGraphqlCollection } from "~/newstore/collections"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
const props = defineProps({
|
||||
picked: { type: Object, default: null },
|
||||
const props = defineProps<{
|
||||
picked: Picked | null
|
||||
// Whether the viewing context is related to picking (activates 'select' events)
|
||||
saveRequest: { type: Boolean, default: false },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
collection: { type: Object, default: () => ({}) },
|
||||
isFiltered: Boolean,
|
||||
})
|
||||
saveRequest: boolean
|
||||
collectionIndex: number | null
|
||||
collection: HoppCollection
|
||||
isFiltered: boolean
|
||||
}>()
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const toast = useToast()
|
||||
@@ -248,7 +271,23 @@ const emit = defineEmits<{
|
||||
(e: "add-request", i: any): void
|
||||
(e: "add-folder", i: any): void
|
||||
(e: "edit-folder", i: any): void
|
||||
(
|
||||
e: "edit-properties",
|
||||
payload: {
|
||||
collectionIndex: string | null
|
||||
collection: HoppCollection
|
||||
}
|
||||
): void
|
||||
(e: "edit-collection"): void
|
||||
(e: "select-request", i: any): void
|
||||
(
|
||||
e: "drop-request",
|
||||
payload: {
|
||||
folderPath: string
|
||||
requestIndex: string
|
||||
collectionIndex: number | null
|
||||
}
|
||||
): void
|
||||
}>()
|
||||
|
||||
// Template refs
|
||||
@@ -324,6 +363,10 @@ const dropEvent = ({ dataTransfer }: any) => {
|
||||
dragging.value = !dragging.value
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
moveGraphqlRequest(folderPath, requestIndex, `${props.collectionIndex}`)
|
||||
emit("drop-request", {
|
||||
folderPath,
|
||||
requestIndex,
|
||||
collectionIndex: props.collectionIndex,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,13 +37,14 @@ import { ref, watch } from "vue"
|
||||
import { editGraphqlCollection } from "~/newstore/collections"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
editingCollection: { type: Object, default: () => ({}) },
|
||||
editingCollectionIndex: { type: Number, default: null },
|
||||
editingCollectionName: { type: String, default: null },
|
||||
})
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
editingCollectionIndex: number | null
|
||||
editingCollection: HoppCollection | null
|
||||
editingCollectionName: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
|
||||
@@ -32,52 +32,47 @@
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { editGraphqlFolder } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
folder: { type: Object, default: () => ({}) },
|
||||
folderPath: { type: String, default: null },
|
||||
editingFolderName: { type: String, default: null },
|
||||
},
|
||||
emits: ["hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingFolderName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editFolder() {
|
||||
if (!this.name) {
|
||||
this.toast.error(`${this.t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
editGraphqlFolder(this.folderPath, {
|
||||
...(this.folder as any),
|
||||
name: this.name,
|
||||
})
|
||||
this.hideModal()
|
||||
},
|
||||
hideModal() {
|
||||
this.name = ""
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
folderPath?: string
|
||||
folder: any
|
||||
editingFolderName: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(["hide-modal"])
|
||||
|
||||
const name = ref("")
|
||||
|
||||
watch(
|
||||
() => props.editingFolderName,
|
||||
(val) => {
|
||||
name.value = val
|
||||
}
|
||||
)
|
||||
|
||||
const editFolder = () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
editGraphqlFolder(props.folderPath, {
|
||||
...(props.folder as any),
|
||||
name: name.value,
|
||||
})
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
name.value = ""
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -32,61 +32,55 @@
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue"
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { editGraphqlRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
folderPath: { type: String, default: null },
|
||||
request: { type: Object as PropType<HoppGQLRequest>, default: () => ({}) },
|
||||
requestIndex: { type: Number, default: null },
|
||||
editingRequestName: { type: String, default: null },
|
||||
},
|
||||
emits: ["hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
requestUpdateData: {
|
||||
name: null as any | null,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingRequestName(val) {
|
||||
this.requestUpdateData.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveRequest() {
|
||||
if (!this.requestUpdateData.name) {
|
||||
this.toast.error(`${this.t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
// TODO: Type safety goes brrrr. Proper typing plz
|
||||
const requestUpdated = {
|
||||
...this.$props.request,
|
||||
name: this.$data.requestUpdateData.name || this.$props.request.name,
|
||||
}
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
folderPath?: string
|
||||
requestIndex: number | null
|
||||
request: HoppGQLRequest | null
|
||||
editingRequestName: string
|
||||
}>()
|
||||
|
||||
editGraphqlRequest(this.folderPath, this.requestIndex, requestUpdated)
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
this.hideModal()
|
||||
},
|
||||
hideModal() {
|
||||
this.requestUpdateData = { name: null }
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
const requestUpdateData = ref({ name: null as string | null })
|
||||
|
||||
watch(
|
||||
() => props.editingRequestName,
|
||||
(val) => {
|
||||
requestUpdateData.value.name = val
|
||||
}
|
||||
)
|
||||
|
||||
const saveRequest = () => {
|
||||
if (!requestUpdateData.value.name) {
|
||||
toast.error(`${t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const requestUpdated = {
|
||||
...(props.request as any),
|
||||
name: requestUpdateData.value.name || (props.request as any).name,
|
||||
}
|
||||
|
||||
editGraphqlRequest(props.folderPath, props.requestIndex, requestUpdated)
|
||||
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
requestUpdateData.value = { name: null }
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,24 +10,25 @@
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-center justify-center cursor-pointer"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
:is="collectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
<span class="pointer-events-none flex items-center justify-center px-4">
|
||||
<component
|
||||
:is="collectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -120,6 +121,21 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="propertiesAction"
|
||||
:icon="IconSettings2"
|
||||
:label="t('action.properties')"
|
||||
:shortcut="['P']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-properties', {
|
||||
collectionIndex: collectionIndex,
|
||||
collection: collection,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
@@ -148,7 +164,14 @@
|
||||
@edit-folder="emit('edit-folder', $event)"
|
||||
@edit-request="emit('edit-request', $event)"
|
||||
@duplicate-request="emit('duplicate-request', $event)"
|
||||
@edit-properties="
|
||||
emit('edit-properties', {
|
||||
collectionIndex: `${folderPath}/${String(subFolderIndex)}`,
|
||||
collection: subFolder,
|
||||
})
|
||||
"
|
||||
@select="emit('select', $event)"
|
||||
@select-request="$emit('select-request', $event)"
|
||||
/>
|
||||
<CollectionsGraphqlRequest
|
||||
v-for="(request, index) in folder.requests"
|
||||
@@ -164,6 +187,7 @@
|
||||
@edit-request="emit('edit-request', $event)"
|
||||
@duplicate-request="emit('duplicate-request', $event)"
|
||||
@select="emit('select', $event)"
|
||||
@select-request="$emit('select-request', $event)"
|
||||
/>
|
||||
|
||||
<HoppSmartPlaceholder
|
||||
@@ -197,13 +221,16 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import IconSettings2 from "~icons/lucide/settings-2"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||
import { removeGraphqlFolder } from "~/newstore/collections"
|
||||
import { computed, ref } from "vue"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
@@ -211,16 +238,16 @@ const colorMode = useColorMode()
|
||||
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const props = defineProps({
|
||||
picked: { type: Object, default: null },
|
||||
const props = defineProps<{
|
||||
picked: Picked
|
||||
// Whether the request is in a selectable mode (activates 'select' event)
|
||||
saveRequest: { type: Boolean, default: false },
|
||||
folder: { type: Object, default: () => ({}) },
|
||||
folderIndex: { type: Number, default: null },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
folderPath: { type: String, default: null },
|
||||
isFiltered: Boolean,
|
||||
})
|
||||
saveRequest: boolean
|
||||
folder: HoppCollection
|
||||
folderIndex: number
|
||||
collectionIndex: number
|
||||
folderPath: string
|
||||
isFiltered: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits([
|
||||
"select",
|
||||
@@ -229,6 +256,9 @@ const emit = defineEmits([
|
||||
"add-folder",
|
||||
"edit-folder",
|
||||
"duplicate-request",
|
||||
"edit-properties",
|
||||
"select-request",
|
||||
"drop-request",
|
||||
])
|
||||
|
||||
// Template refs
|
||||
@@ -303,6 +333,11 @@ const dropEvent = ({ dataTransfer }: any) => {
|
||||
dragging.value = !dragging.value
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
moveGraphqlRequest(folderPath, requestIndex, props.folderPath)
|
||||
|
||||
emit("drop-request", {
|
||||
folderPath,
|
||||
requestIndex,
|
||||
collectionIndex: props.folderPath,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||
import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource"
|
||||
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
||||
@@ -25,13 +25,14 @@ import { useReadonlyStream } from "~/composables/stream"
|
||||
|
||||
import { platform } from "~/platform"
|
||||
import {
|
||||
appendGraphqlCollections,
|
||||
graphqlCollections$,
|
||||
setGraphqlCollections,
|
||||
} from "~/newstore/collections"
|
||||
import { hoppGqlCollectionsImporter } from "~/helpers/import-export/import/hoppGql"
|
||||
import { gqlCollectionsExporter } from "~/helpers/import-export/export/gqlCollections"
|
||||
import { gqlCollectionsGistExporter } from "~/helpers/import-export/export/gqlCollectionsGistExporter"
|
||||
import { computed } from "vue"
|
||||
import { hoppGQLImporter } from "~/helpers/import-export/import/hopp"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -60,15 +61,20 @@ const GqlCollectionsHoppImporter: ImporterOrExporter = {
|
||||
showImportFailedError()
|
||||
return
|
||||
}
|
||||
const validatedCollection = await hoppGQLImporter(
|
||||
JSON.stringify(res.right)
|
||||
)()
|
||||
|
||||
handleImportToStore(res.right)
|
||||
if (E.isRight(validatedCollection)) {
|
||||
handleImportToStore(validatedCollection.right)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
importer: "json",
|
||||
})
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_COLLECTION",
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
importer: "json",
|
||||
})
|
||||
}
|
||||
|
||||
emit("hide-modal")
|
||||
},
|
||||
@@ -214,11 +220,9 @@ const showImportFailedError = () => {
|
||||
toast.error(t("import.failed"))
|
||||
}
|
||||
|
||||
const handleImportToStore = async (
|
||||
gqlCollections: HoppCollection<HoppGQLRequest>[]
|
||||
) => {
|
||||
setGraphqlCollections(gqlCollections)
|
||||
toast.success(t("import.success"))
|
||||
const handleImportToStore = async (gqlCollections: HoppCollection[]) => {
|
||||
appendGraphqlCollections(gqlCollections)
|
||||
toast.success(t("state.file_imported"))
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -9,38 +9,41 @@
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex w-16 cursor-pointer items-center justify-center truncate px-2"
|
||||
<div
|
||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<component
|
||||
:is="isSelected ? IconCheckCircle : IconFile"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex min-w-0 flex-1 cursor-pointer items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
<span
|
||||
class="pointer-events-none flex w-8 items-center justify-center truncate px-6"
|
||||
>
|
||||
<component
|
||||
:is="isSelected ? IconCheckCircle : IconFile"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
class="pointer-events-none flex min-w-0 flex-1 items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||
></span>
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<tippy
|
||||
@@ -134,8 +137,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { PropType, computed, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
@@ -175,7 +177,12 @@ const isActive = computed(() => {
|
||||
})
|
||||
|
||||
// TODO: Better types please
|
||||
const emit = defineEmits(["select", "edit-request", "duplicate-request"])
|
||||
const emit = defineEmits([
|
||||
"select",
|
||||
"edit-request",
|
||||
"duplicate-request",
|
||||
"select-request",
|
||||
])
|
||||
|
||||
const dragging = ref(false)
|
||||
const confirmRemove = ref(false)
|
||||
@@ -199,36 +206,11 @@ const selectRequest = () => {
|
||||
if (props.saveRequest) {
|
||||
pick()
|
||||
} else {
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
emit("select-request", {
|
||||
request: props.request,
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
})
|
||||
|
||||
// Switch to that request if that request is open
|
||||
if (possibleTab) {
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
return
|
||||
}
|
||||
|
||||
tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
},
|
||||
request: cloneDeep(
|
||||
makeGQLRequest({
|
||||
name: props.request.name,
|
||||
url: props.request.url,
|
||||
query: props.request.query,
|
||||
headers: props.request.headers,
|
||||
variables: props.request.variables,
|
||||
auth: props.request.auth,
|
||||
})
|
||||
),
|
||||
isDirty: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,10 @@
|
||||
@edit-request="editRequest($event)"
|
||||
@duplicate-request="duplicateRequest($event)"
|
||||
@select-collection="$emit('use-collection', collection)"
|
||||
@edit-properties="editProperties($event)"
|
||||
@select="$emit('select', $event)"
|
||||
@select-request="selectRequest($event)"
|
||||
@drop-request="dropRequest($event)"
|
||||
/>
|
||||
</div>
|
||||
<HoppSmartPlaceholder
|
||||
@@ -142,19 +145,27 @@
|
||||
v-if="showModalImportExport"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
<CollectionsProperties
|
||||
:show="showModalEditProperties"
|
||||
:editing-properties="editingProperties"
|
||||
@hide-modal="displayModalEditProperties(false)"
|
||||
@set-collection-properties="setCollectionProperties"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// TODO: TypeScript + Script Setup this :)
|
||||
import { defineComponent } from "vue"
|
||||
import { cloneDeep, clone } from "lodash-es"
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from "vue"
|
||||
import { clone, cloneDeep } from "lodash-es"
|
||||
import {
|
||||
graphqlCollections$,
|
||||
addGraphqlFolder,
|
||||
saveGraphqlRequestAs,
|
||||
cascadeParentCollectionForHeaderAuth,
|
||||
editGraphqlCollection,
|
||||
editGraphqlFolder,
|
||||
moveGraphqlRequest,
|
||||
} from "~/newstore/collections"
|
||||
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
@@ -164,213 +175,448 @@ import { useColorMode } from "@composables/theming"
|
||||
import { platform } from "~/platform"
|
||||
import { useService } from "dioc/vue"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { computed } from "vue"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
makeGQLRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { getRequestsByPath } from "~/helpers/collection/request"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
// Whether to activate the ability to pick items (activates 'select' events)
|
||||
saveRequest: { type: Boolean, default: false },
|
||||
picked: { type: Object, default: null },
|
||||
},
|
||||
emits: ["select", "use-collection"],
|
||||
setup() {
|
||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
const tabs = useService(GQLTabService)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
return {
|
||||
collections,
|
||||
colorMode,
|
||||
t,
|
||||
tabs,
|
||||
IconPlus,
|
||||
IconHelpCircle,
|
||||
IconImport,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModalAdd: false,
|
||||
showModalEdit: false,
|
||||
showModalImportExport: false,
|
||||
showModalAddRequest: false,
|
||||
showModalAddFolder: false,
|
||||
showModalEditFolder: false,
|
||||
showModalEditRequest: false,
|
||||
editingCollection: undefined,
|
||||
editingCollectionIndex: undefined,
|
||||
editingFolder: undefined,
|
||||
editingFolderName: undefined,
|
||||
editingFolderIndex: undefined,
|
||||
editingFolderPath: undefined,
|
||||
editingRequest: undefined,
|
||||
editingRequestIndex: undefined,
|
||||
filterText: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredCollections() {
|
||||
const collections = clone(this.collections)
|
||||
defineProps<{
|
||||
// Whether to activate the ability to pick items (activates 'select' events)
|
||||
saveRequest: boolean
|
||||
picked: Picked
|
||||
}>()
|
||||
|
||||
if (!this.filterText) return collections
|
||||
const collections = useReadonlyStream(graphqlCollections$, [], "deep")
|
||||
const colorMode = useColorMode()
|
||||
const tabs = useService(GQLTabService)
|
||||
|
||||
const filterText = this.filterText.toLowerCase()
|
||||
const filteredCollections = []
|
||||
const showModalAdd = ref(false)
|
||||
const showModalEdit = ref(false)
|
||||
const showModalImportExport = ref(false)
|
||||
const showModalAddRequest = ref(false)
|
||||
const showModalAddFolder = ref(false)
|
||||
const showModalEditFolder = ref(false)
|
||||
const showModalEditRequest = ref(false)
|
||||
const showModalEditProperties = ref(false)
|
||||
|
||||
for (const collection of collections) {
|
||||
const filteredRequests = []
|
||||
const filteredFolders = []
|
||||
for (const request of collection.requests) {
|
||||
if (request.name.toLowerCase().includes(filterText))
|
||||
filteredRequests.push(request)
|
||||
}
|
||||
for (const folder of collection.folders) {
|
||||
const filteredFolderRequests = []
|
||||
for (const request of folder.requests) {
|
||||
if (request.name.toLowerCase().includes(filterText))
|
||||
filteredFolderRequests.push(request)
|
||||
}
|
||||
if (filteredFolderRequests.length > 0) {
|
||||
const filteredFolder = Object.assign({}, folder)
|
||||
filteredFolder.requests = filteredFolderRequests
|
||||
filteredFolders.push(filteredFolder)
|
||||
}
|
||||
}
|
||||
const editingCollection = ref<HoppCollection | null>(null)
|
||||
const editingCollectionIndex = ref<number | null>(null)
|
||||
const editingFolder = ref<HoppCollection | null>(null)
|
||||
const editingFolderName = ref("")
|
||||
const editingFolderIndex = ref<number | null>(null)
|
||||
const editingFolderPath = ref("")
|
||||
const editingRequest = ref<HoppGQLRequest | null>(null)
|
||||
const editingRequestIndex = ref<number | null>(null)
|
||||
|
||||
if (filteredRequests.length + filteredFolders.length > 0) {
|
||||
const filteredCollection = Object.assign({}, collection)
|
||||
filteredCollection.requests = filteredRequests
|
||||
filteredCollection.folders = filteredFolders
|
||||
filteredCollections.push(filteredCollection)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCollections
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
displayModalAdd(shouldDisplay) {
|
||||
this.showModalAdd = shouldDisplay
|
||||
},
|
||||
displayModalEdit(shouldDisplay) {
|
||||
this.showModalEdit = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalImportExport(shouldDisplay) {
|
||||
this.showModalImportExport = shouldDisplay
|
||||
},
|
||||
displayModalAddRequest(shouldDisplay) {
|
||||
this.showModalAddRequest = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalAddFolder(shouldDisplay) {
|
||||
this.showModalAddFolder = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalEditFolder(shouldDisplay) {
|
||||
this.showModalEditFolder = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalEditRequest(shouldDisplay) {
|
||||
this.showModalEditRequest = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
editCollection(collection, collectionIndex) {
|
||||
this.$data.editingCollection = collection
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.displayModalEdit(true)
|
||||
},
|
||||
onAddRequest({ name, path, index }) {
|
||||
const newRequest = {
|
||||
...this.tabs.currentActiveTab.value.document.request,
|
||||
name,
|
||||
}
|
||||
|
||||
saveGraphqlRequestAs(path, newRequest)
|
||||
|
||||
this.tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
requestIndex: index,
|
||||
},
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
})
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SAVE_REQUEST",
|
||||
platform: "gql",
|
||||
createdNow: true,
|
||||
workspaceType: "personal",
|
||||
})
|
||||
|
||||
this.displayModalAddRequest(false)
|
||||
},
|
||||
addRequest(payload) {
|
||||
const { path } = payload
|
||||
this.$data.editingFolderPath = path
|
||||
this.displayModalAddRequest(true)
|
||||
},
|
||||
onAddFolder({ name, path }) {
|
||||
addGraphqlFolder(name, path)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_CREATE_COLLECTION",
|
||||
isRootCollection: false,
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
})
|
||||
|
||||
this.displayModalAddFolder(false)
|
||||
},
|
||||
addFolder(payload) {
|
||||
const { path } = payload
|
||||
this.$data.editingFolderPath = path
|
||||
this.displayModalAddFolder(true)
|
||||
},
|
||||
editFolder(payload) {
|
||||
const { folder, folderPath } = payload
|
||||
this.editingFolder = folder
|
||||
this.editingFolderPath = folderPath
|
||||
this.displayModalEditFolder(true)
|
||||
},
|
||||
editRequest(payload) {
|
||||
const {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
} = payload
|
||||
this.$data.editingFolderPath = folderPath
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.$data.editingFolderIndex = folderIndex
|
||||
this.$data.editingFolderName = folderName
|
||||
this.$data.editingRequest = request
|
||||
this.$data.editingRequestIndex = requestIndex
|
||||
this.displayModalEditRequest(true)
|
||||
},
|
||||
resetSelectedData() {
|
||||
this.$data.editingCollection = undefined
|
||||
this.$data.editingCollectionIndex = undefined
|
||||
this.$data.editingFolder = undefined
|
||||
this.$data.editingFolderIndex = undefined
|
||||
this.$data.editingRequest = undefined
|
||||
this.$data.editingRequestIndex = undefined
|
||||
},
|
||||
duplicateRequest({ folderPath, request }) {
|
||||
saveGraphqlRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.t("action.duplicate")}`,
|
||||
})
|
||||
},
|
||||
},
|
||||
const editingProperties = ref<{
|
||||
collection: HoppCollection | null
|
||||
isRootCollection: boolean
|
||||
path: string
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>({
|
||||
collection: null,
|
||||
isRootCollection: false,
|
||||
path: "",
|
||||
inheritedProperties: undefined,
|
||||
})
|
||||
|
||||
const filterText = ref("")
|
||||
|
||||
const filteredCollections = computed(() => {
|
||||
const collectionsClone = clone(collections.value)
|
||||
|
||||
if (!filterText.value) return collectionsClone
|
||||
|
||||
const filterTextLower = filterText.value.toLowerCase()
|
||||
const filteredCollections = []
|
||||
|
||||
for (const collection of collectionsClone) {
|
||||
const filteredRequests = []
|
||||
const filteredFolders = []
|
||||
|
||||
for (const request of collection.requests) {
|
||||
if (request.name.toLowerCase().includes(filterTextLower))
|
||||
filteredRequests.push(request)
|
||||
}
|
||||
|
||||
for (const folder of collection.folders) {
|
||||
const filteredFolderRequests = []
|
||||
for (const request of folder.requests) {
|
||||
if (request.name.toLowerCase().includes(filterTextLower))
|
||||
filteredFolderRequests.push(request)
|
||||
}
|
||||
if (filteredFolderRequests.length > 0) {
|
||||
const filteredFolder = { ...folder }
|
||||
filteredFolder.requests = filteredFolderRequests
|
||||
filteredFolders.push(filteredFolder)
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredRequests.length + filteredFolders.length > 0) {
|
||||
const filteredCollection = { ...collection }
|
||||
filteredCollection.requests = filteredRequests
|
||||
filteredCollection.folders = filteredFolders
|
||||
filteredCollections.push(filteredCollection)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCollections
|
||||
})
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
showModalAdd.value = shouldDisplay
|
||||
}
|
||||
|
||||
const displayModalEdit = (shouldDisplay: boolean) => {
|
||||
showModalEdit.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalImportExport = (shouldDisplay: boolean) => {
|
||||
showModalImportExport.value = shouldDisplay
|
||||
}
|
||||
|
||||
const displayModalAddRequest = (shouldDisplay: boolean) => {
|
||||
showModalAddRequest.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalAddFolder = (shouldDisplay: boolean) => {
|
||||
showModalAddFolder.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalEditFolder = (shouldDisplay: boolean) => {
|
||||
showModalEditFolder.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalEditRequest = (shouldDisplay: boolean) => {
|
||||
showModalEditRequest.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalEditProperties = (show: boolean) => {
|
||||
showModalEditProperties.value = show
|
||||
|
||||
if (!show) resetSelectedData()
|
||||
}
|
||||
|
||||
const editCollection = (
|
||||
collection: HoppCollection,
|
||||
collectionIndex: number
|
||||
) => {
|
||||
editingCollection.value = collection
|
||||
editingCollectionIndex.value = collectionIndex
|
||||
displayModalEdit(true)
|
||||
}
|
||||
|
||||
const onAddRequest = ({
|
||||
name,
|
||||
path,
|
||||
index,
|
||||
}: {
|
||||
name: string
|
||||
path: string
|
||||
index: number
|
||||
}) => {
|
||||
const newRequest = {
|
||||
...tabs.currentActiveTab.value.document.request,
|
||||
name,
|
||||
}
|
||||
|
||||
saveGraphqlRequestAs(path, newRequest)
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
path,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
requestIndex: index,
|
||||
},
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SAVE_REQUEST",
|
||||
platform: "gql",
|
||||
createdNow: true,
|
||||
workspaceType: "personal",
|
||||
})
|
||||
|
||||
displayModalAddRequest(false)
|
||||
}
|
||||
|
||||
const addRequest = (payload: { path: string }) => {
|
||||
const { path } = payload
|
||||
editingFolderPath.value = path
|
||||
displayModalAddRequest(true)
|
||||
}
|
||||
|
||||
const onAddFolder = ({
|
||||
name,
|
||||
path,
|
||||
}: {
|
||||
name: string
|
||||
path: string | undefined
|
||||
}) => {
|
||||
addGraphqlFolder(name, path ?? "0")
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_CREATE_COLLECTION",
|
||||
isRootCollection: false,
|
||||
platform: "gql",
|
||||
workspaceType: "personal",
|
||||
})
|
||||
|
||||
displayModalAddFolder(false)
|
||||
}
|
||||
|
||||
const addFolder = (payload: { path: string }) => {
|
||||
const { path } = payload
|
||||
editingFolderPath.value = path
|
||||
displayModalAddFolder(true)
|
||||
}
|
||||
|
||||
const editFolder = (payload: {
|
||||
folder: HoppCollection
|
||||
folderPath: string
|
||||
}) => {
|
||||
const { folder, folderPath } = payload
|
||||
editingFolder.value = folder
|
||||
editingFolderPath.value = folderPath
|
||||
displayModalEditFolder(true)
|
||||
}
|
||||
|
||||
const editRequest = (payload: {
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName: string
|
||||
request: HoppGQLRequest
|
||||
requestIndex: number
|
||||
folderPath: string
|
||||
}) => {
|
||||
const {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
} = payload
|
||||
editingFolderPath.value = folderPath
|
||||
editingCollectionIndex.value = collectionIndex
|
||||
editingFolderIndex.value = folderIndex
|
||||
editingFolderName.value = folderName
|
||||
editingRequest.value = request
|
||||
editingRequestIndex.value = requestIndex
|
||||
displayModalEditRequest(true)
|
||||
}
|
||||
|
||||
const duplicateRequest = ({
|
||||
folderPath,
|
||||
request,
|
||||
}: {
|
||||
folderPath: string
|
||||
request: HoppGQLRequest
|
||||
}) => {
|
||||
saveGraphqlRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${t("action.duplicate")}`,
|
||||
})
|
||||
}
|
||||
|
||||
const selectRequest = ({
|
||||
request,
|
||||
folderPath,
|
||||
requestIndex,
|
||||
}: {
|
||||
request: HoppGQLRequest
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}) => {
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath,
|
||||
requestIndex: requestIndex,
|
||||
})
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath,
|
||||
"graphql"
|
||||
)
|
||||
// Switch to that request if that request is open
|
||||
if (possibleTab) {
|
||||
tabs.setActiveTab(possibleTab.value.id)
|
||||
return
|
||||
}
|
||||
|
||||
tabs.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath,
|
||||
requestIndex: requestIndex,
|
||||
},
|
||||
request: cloneDeep(
|
||||
makeGQLRequest({
|
||||
name: request.name,
|
||||
url: request.url,
|
||||
query: request.query,
|
||||
headers: request.headers,
|
||||
variables: request.variables,
|
||||
auth: request.auth,
|
||||
})
|
||||
),
|
||||
isDirty: false,
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const dropRequest = ({
|
||||
folderPath,
|
||||
requestIndex,
|
||||
collectionIndex,
|
||||
}: {
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
collectionIndex: number
|
||||
}) => {
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
`${collectionIndex}`,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex: Number(requestIndex),
|
||||
})
|
||||
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.saveContext = {
|
||||
originLocation: "user-collection",
|
||||
folderPath: `${collectionIndex}`,
|
||||
requestIndex: getRequestsByPath(collections.value, `${collectionIndex}`)
|
||||
.length,
|
||||
}
|
||||
|
||||
possibleTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
moveGraphqlRequest(folderPath, requestIndex, `${collectionIndex}`)
|
||||
|
||||
toast.success(`${t("request.moved")}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the collection is already in the root
|
||||
* @param id - path of the collection
|
||||
* @returns boolean - true if the collection is already in the root
|
||||
*/
|
||||
const isAlreadyInRoot = (id: string) => {
|
||||
const indexPath = id.split("/")
|
||||
return indexPath.length === 1
|
||||
}
|
||||
|
||||
const editProperties = ({
|
||||
collectionIndex,
|
||||
collection,
|
||||
}: {
|
||||
collectionIndex: string | null
|
||||
collection: HoppCollection | null
|
||||
}) => {
|
||||
if (collectionIndex === null || collection === null) return
|
||||
|
||||
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||
let inheritedProperties = {}
|
||||
|
||||
if (parentIndex) {
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
parentIndex,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
} as HoppInheritedProperty
|
||||
}
|
||||
|
||||
editingProperties.value = {
|
||||
collection,
|
||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||
path: collectionIndex,
|
||||
inheritedProperties,
|
||||
}
|
||||
|
||||
displayModalEditProperties(true)
|
||||
}
|
||||
|
||||
const setCollectionProperties = (newCollection: {
|
||||
collection: HoppCollection
|
||||
path: string
|
||||
isRootCollection: boolean
|
||||
}) => {
|
||||
const { collection, path, isRootCollection } = newCollection
|
||||
if (isRootCollection) {
|
||||
editGraphqlCollection(parseInt(path), collection)
|
||||
} else {
|
||||
editGraphqlFolder(path, collection)
|
||||
}
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
path,
|
||||
"graphql"
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
path,
|
||||
{
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
"graphql"
|
||||
)
|
||||
})
|
||||
|
||||
displayModalEditProperties(false)
|
||||
}
|
||||
const resetSelectedData = () => {
|
||||
editingCollection.value = null
|
||||
editingCollectionIndex.value = null
|
||||
editingFolder.value = null
|
||||
editingFolderIndex.value = null
|
||||
editingRequest.value = null
|
||||
editingRequestIndex.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
@add-request="addRequest"
|
||||
@edit-collection="editCollection"
|
||||
@edit-folder="editFolder"
|
||||
@edit-properties="editProperties"
|
||||
@export-data="exportData"
|
||||
@remove-collection="removeCollection"
|
||||
@remove-folder="removeFolder"
|
||||
@@ -69,6 +70,7 @@
|
||||
@add-folder="addFolder"
|
||||
@edit-collection="editCollection"
|
||||
@edit-folder="editFolder"
|
||||
@edit-properties="editProperties"
|
||||
@export-data="exportData"
|
||||
@remove-collection="removeCollection"
|
||||
@remove-folder="removeFolder"
|
||||
@@ -151,6 +153,12 @@
|
||||
:show="showTeamModalAdd"
|
||||
@hide-modal="displayTeamModalAdd(false)"
|
||||
/>
|
||||
<CollectionsProperties
|
||||
:show="showModalEditProperties"
|
||||
:editing-properties="editingProperties"
|
||||
@hide-modal="displayModalEditProperties(false)"
|
||||
@set-collection-properties="setCollectionProperties"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -181,10 +189,13 @@ import {
|
||||
moveRESTFolder,
|
||||
navigateToFolderWithIndexPath,
|
||||
restCollectionStore,
|
||||
cascadeParentCollectionForHeaderAuth,
|
||||
} from "~/newstore/collections"
|
||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequest,
|
||||
makeCollection,
|
||||
} from "@hoppscotch/data"
|
||||
@@ -193,10 +204,10 @@ import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
createNewRootCollection,
|
||||
createChildCollection,
|
||||
renameCollection,
|
||||
deleteCollection,
|
||||
moveRESTTeamCollection,
|
||||
updateOrderRESTTeamCollection,
|
||||
updateTeamCollection,
|
||||
} from "~/helpers/backend/mutations/TeamCollection"
|
||||
import {
|
||||
updateTeamRequest,
|
||||
@@ -220,6 +231,7 @@ import {
|
||||
getFoldersByPath,
|
||||
resolveSaveContextOnCollectionReorder,
|
||||
updateSaveContextForAffectedRequests,
|
||||
updateInheritedPropertiesForAffectedRequests,
|
||||
resetTeamRequestsContext,
|
||||
} from "~/helpers/collection/collection"
|
||||
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||
@@ -227,6 +239,7 @@ import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -266,15 +279,11 @@ const collectionsType = ref<CollectionType>({
|
||||
})
|
||||
|
||||
// Collection Data
|
||||
const editingCollection = ref<
|
||||
HoppCollection<HoppRESTRequest> | TeamCollection | null
|
||||
>(null)
|
||||
const editingCollection = ref<HoppCollection | TeamCollection | null>(null)
|
||||
const editingCollectionName = ref<string | null>(null)
|
||||
const editingCollectionIndex = ref<number | null>(null)
|
||||
const editingCollectionID = ref<string | null>(null)
|
||||
const editingFolder = ref<
|
||||
HoppCollection<HoppRESTRequest> | TeamCollection | null
|
||||
>(null)
|
||||
const editingFolder = ref<HoppCollection | TeamCollection | null>(null)
|
||||
const editingFolderName = ref<string | null>(null)
|
||||
const editingFolderPath = ref<string | null>(null)
|
||||
const editingRequest = ref<HoppRESTRequest | null>(null)
|
||||
@@ -282,6 +291,18 @@ const editingRequestName = ref("")
|
||||
const editingRequestIndex = ref<number | null>(null)
|
||||
const editingRequestID = ref<string | null>(null)
|
||||
|
||||
const editingProperties = ref<{
|
||||
collection: Omit<HoppCollection, "v"> | TeamCollection | null
|
||||
isRootCollection: boolean
|
||||
path: string
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>({
|
||||
collection: null,
|
||||
isRootCollection: false,
|
||||
path: "",
|
||||
inheritedProperties: undefined,
|
||||
})
|
||||
|
||||
const confirmModalTitle = ref<string | null>(null)
|
||||
|
||||
const filterTexts = ref("")
|
||||
@@ -520,6 +541,7 @@ const showModalEditCollection = ref(false)
|
||||
const showModalEditFolder = ref(false)
|
||||
const showModalEditRequest = ref(false)
|
||||
const showModalImportExport = ref(false)
|
||||
const showModalEditProperties = ref(false)
|
||||
const showConfirmModal = ref(false)
|
||||
const showTeamModalAdd = ref(false)
|
||||
|
||||
@@ -565,6 +587,12 @@ const displayModalImportExport = (show: boolean) => {
|
||||
if (!show) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayModalEditProperties = (show: boolean) => {
|
||||
showModalEditProperties.value = show
|
||||
|
||||
if (!show) resetSelectedData()
|
||||
}
|
||||
|
||||
const displayConfirmModal = (show: boolean) => {
|
||||
showConfirmModal.value = show
|
||||
|
||||
@@ -584,6 +612,11 @@ const addNewRootCollection = (name: string) => {
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
@@ -625,7 +658,7 @@ const addNewRootCollection = (name: string) => {
|
||||
|
||||
const addRequest = (payload: {
|
||||
path: string
|
||||
folder: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
folder: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
const { path, folder } = payload
|
||||
editingFolder.value = folder
|
||||
@@ -639,11 +672,13 @@ const onAddRequest = (requestName: string) => {
|
||||
name: requestName,
|
||||
}
|
||||
|
||||
const path = editingFolderPath.value
|
||||
if (!path) return
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const path = editingFolderPath.value
|
||||
if (!path) return
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(path, "rest")
|
||||
|
||||
tabs.createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
@@ -652,6 +687,10 @@ const onAddRequest = (requestName: string) => {
|
||||
folderPath: path,
|
||||
requestIndex: insertionIndex,
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
@@ -692,7 +731,8 @@ const onAddRequest = (requestName: string) => {
|
||||
},
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(path)
|
||||
tabs.createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
@@ -702,6 +742,10 @@ const onAddRequest = (requestName: string) => {
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
|
||||
modalLoadingState.value = false
|
||||
@@ -714,7 +758,7 @@ const onAddRequest = (requestName: string) => {
|
||||
|
||||
const addFolder = (payload: {
|
||||
path: string
|
||||
folder: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
folder: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
const { path, folder } = payload
|
||||
editingFolder.value = folder
|
||||
@@ -773,15 +817,13 @@ const onAddFolder = (folderName: string) => {
|
||||
|
||||
const editCollection = (payload: {
|
||||
collectionIndex: string
|
||||
collection: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
collection: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
const { collectionIndex, collection } = payload
|
||||
editingCollection.value = collection
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
editingCollectionIndex.value = parseInt(collectionIndex)
|
||||
editingCollectionName.value = (
|
||||
collection as HoppCollection<HoppRESTRequest>
|
||||
).name
|
||||
editingCollectionName.value = (collection as HoppCollection).name
|
||||
} else {
|
||||
editingCollectionName.value = (collection as TeamCollection).title
|
||||
}
|
||||
@@ -816,7 +858,7 @@ const updateEditingCollection = (newName: string) => {
|
||||
modalLoadingState.value = true
|
||||
|
||||
pipe(
|
||||
renameCollection(editingCollection.value.id, newName),
|
||||
updateTeamCollection(editingCollection.value.id, undefined, newName),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
@@ -834,13 +876,13 @@ const updateEditingCollection = (newName: string) => {
|
||||
|
||||
const editFolder = (payload: {
|
||||
folderPath: string | undefined
|
||||
folder: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
folder: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
const { folderPath, folder } = payload
|
||||
editingFolder.value = folder
|
||||
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||
editingFolderPath.value = folderPath
|
||||
editingFolderName.value = (folder as HoppCollection<HoppRESTRequest>).name
|
||||
editingFolderName.value = (folder as HoppCollection).name
|
||||
} else {
|
||||
editingFolderName.value = (folder as TeamCollection).title
|
||||
}
|
||||
@@ -854,7 +896,7 @@ const updateEditingFolder = (newName: string) => {
|
||||
if (!editingFolderPath.value) return
|
||||
|
||||
editRESTFolder(editingFolderPath.value, {
|
||||
...(editingFolder.value as HoppCollection<HoppRESTRequest>),
|
||||
...(editingFolder.value as HoppCollection),
|
||||
name: newName,
|
||||
})
|
||||
displayModalEditFolder(false)
|
||||
@@ -865,7 +907,7 @@ const updateEditingFolder = (newName: string) => {
|
||||
/* renameCollection can be used to rename both collections and folders
|
||||
since folder is treated as collection in the BE. */
|
||||
pipe(
|
||||
renameCollection(editingFolder.value.id, newName),
|
||||
updateTeamCollection(editingFolder.value.id, undefined, newName),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
if (err.error === "team_coll/short_title") {
|
||||
@@ -1279,16 +1321,18 @@ const selectPicked = (payload: Picked | null) => {
|
||||
*/
|
||||
const selectRequest = (selectedRequest: {
|
||||
request: HoppRESTRequest
|
||||
folderPath: string | undefined
|
||||
folderPath: string
|
||||
requestIndex: string
|
||||
isActive: boolean
|
||||
}) => {
|
||||
const { request, folderPath, requestIndex } = selectedRequest
|
||||
|
||||
// If there is a request with this save context, switch into it
|
||||
let possibleTab = null
|
||||
|
||||
if (collectionsType.value.type === "team-collections") {
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
|
||||
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
@@ -1302,10 +1346,19 @@ const selectRequest = (selectedRequest: {
|
||||
saveContext: {
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
collectionID: folderPath,
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath,
|
||||
"rest"
|
||||
)
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex: parseInt(requestIndex),
|
||||
@@ -1323,6 +1376,10 @@ const selectRequest = (selectedRequest: {
|
||||
folderPath: folderPath!,
|
||||
requestIndex: parseInt(requestIndex),
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1349,16 +1406,17 @@ const dropRequest = (payload: {
|
||||
}) => {
|
||||
const { folderPath, requestIndex, destinationCollectionIndex } = payload
|
||||
|
||||
if (!requestIndex || !destinationCollectionIndex) return
|
||||
if (!requestIndex || !destinationCollectionIndex || !folderPath) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||
moveRESTRequest(
|
||||
folderPath,
|
||||
pathToLastIndex(requestIndex),
|
||||
destinationCollectionIndex
|
||||
let possibleTab = null
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
destinationCollectionIndex,
|
||||
"rest"
|
||||
)
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex: pathToLastIndex(requestIndex),
|
||||
@@ -1374,6 +1432,11 @@ const dropRequest = (payload: {
|
||||
destinationCollectionIndex
|
||||
).length,
|
||||
}
|
||||
|
||||
possibleTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
// When it's drop it's basically getting deleted from last folder. reordering last folder accordingly
|
||||
@@ -1383,6 +1446,11 @@ const dropRequest = (payload: {
|
||||
folderPath,
|
||||
length: getRequestsByPath(myCollections.value, folderPath).length,
|
||||
})
|
||||
moveRESTRequest(
|
||||
folderPath,
|
||||
pathToLastIndex(requestIndex),
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
toast.success(`${t("request.moved")}`)
|
||||
draggingToRoot.value = false
|
||||
@@ -1406,8 +1474,12 @@ const dropRequest = (payload: {
|
||||
requestMoveLoading.value.indexOf(requestIndex),
|
||||
1
|
||||
)
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||
possibleTab = tabs.getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
@@ -1417,6 +1489,10 @@ const dropRequest = (payload: {
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
}
|
||||
possibleTab.value.document.inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
toast.success(`${t("request.moved")}`)
|
||||
}
|
||||
@@ -1537,6 +1613,22 @@ const dropCollection = (payload: {
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`
|
||||
)
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`,
|
||||
"rest"
|
||||
)
|
||||
|
||||
const inheritedProperty = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
|
||||
draggingToRoot.value = false
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1562,6 +1654,22 @@ const dropCollection = (payload: {
|
||||
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||
1
|
||||
)
|
||||
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
const inheritedProperty = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
`${destinationCollectionIndex}`,
|
||||
inheritedProperty,
|
||||
"rest"
|
||||
)
|
||||
}
|
||||
)
|
||||
)()
|
||||
@@ -1846,13 +1954,11 @@ const initializeDownloadCollection = async (
|
||||
* Triggered by the export button in the tippy menu
|
||||
* @param collection - Collection or folder to be exported
|
||||
*/
|
||||
const exportData = async (
|
||||
collection: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
) => {
|
||||
const exportData = async (collection: HoppCollection | TeamCollection) => {
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const collectionJSON = JSON.stringify(collection)
|
||||
|
||||
const name = (collection as HoppCollection<HoppRESTRequest>).name
|
||||
const name = (collection as HoppCollection).name
|
||||
|
||||
initializeDownloadCollection(collectionJSON, name)
|
||||
} else {
|
||||
@@ -1893,6 +1999,164 @@ const shareRequest = ({ request }: { request: HoppRESTRequest }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const editProperties = (payload: {
|
||||
collectionIndex: string
|
||||
collection: HoppCollection | TeamCollection
|
||||
}) => {
|
||||
const { collection, collectionIndex } = payload
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||
|
||||
let inheritedProperties = {
|
||||
auth: {
|
||||
parentID: "",
|
||||
parentName: "",
|
||||
inheritedAuth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
},
|
||||
headers: [
|
||||
{
|
||||
parentID: "",
|
||||
parentName: "",
|
||||
inheritedHeaders: [],
|
||||
},
|
||||
],
|
||||
} as HoppInheritedProperty
|
||||
|
||||
if (parentIndex) {
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
parentIndex,
|
||||
"rest"
|
||||
)
|
||||
|
||||
inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
editingProperties.value = {
|
||||
collection,
|
||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||
path: collectionIndex,
|
||||
inheritedProperties,
|
||||
}
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||
|
||||
const data = (collection as TeamCollection).data
|
||||
? JSON.parse((collection as TeamCollection).data ?? "")
|
||||
: null
|
||||
|
||||
let inheritedProperties = undefined
|
||||
let coll = {
|
||||
id: collection.id,
|
||||
name: (collection as TeamCollection).title,
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
} as HoppRESTAuth,
|
||||
headers: [] as HoppRESTHeaders,
|
||||
folders: null,
|
||||
requests: null,
|
||||
}
|
||||
|
||||
if (parentIndex) {
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(parentIndex)
|
||||
|
||||
inheritedProperties = {
|
||||
auth,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
coll = {
|
||||
...coll,
|
||||
auth: data.auth,
|
||||
headers: data.headers as HoppRESTHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
editingProperties.value = {
|
||||
collection: coll,
|
||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||
path: collectionIndex,
|
||||
inheritedProperties,
|
||||
}
|
||||
}
|
||||
|
||||
displayModalEditProperties(true)
|
||||
}
|
||||
|
||||
const setCollectionProperties = (newCollection: {
|
||||
collection: HoppCollection
|
||||
path: string
|
||||
isRootCollection: boolean
|
||||
}) => {
|
||||
const { collection, path, isRootCollection } = newCollection
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (isRootCollection) {
|
||||
editRESTCollection(parseInt(path), collection)
|
||||
} else {
|
||||
editRESTFolder(path, collection)
|
||||
}
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(path, "rest")
|
||||
|
||||
nextTick(() => {
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
path,
|
||||
{
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
"rest"
|
||||
)
|
||||
})
|
||||
toast.success(t("collection.properties_updated"))
|
||||
} else if (hasTeamWriteAccess.value && collection.id) {
|
||||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
}
|
||||
pipe(
|
||||
updateTeamCollection(collection.id, JSON.stringify(data), undefined),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
},
|
||||
() => {
|
||||
toast.success(t("collection.properties_updated"))
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
||||
//This is a hack to update the inherited properties of the requests if there an tab opened
|
||||
// since it takes a little bit of time to update the collection tree
|
||||
setTimeout(() => {
|
||||
const { auth, headers } =
|
||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(path)
|
||||
updateInheritedPropertiesForAffectedRequests(
|
||||
path,
|
||||
{
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
"rest",
|
||||
"team"
|
||||
)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
displayModalEditProperties(false)
|
||||
}
|
||||
|
||||
const resolveConfirmModal = (title: string | null) => {
|
||||
if (title === `${t("confirm.remove_collection")}`) onRemoveCollection()
|
||||
else if (title === `${t("confirm.remove_request")}`) onRemoveRequest()
|
||||
|
||||
@@ -37,6 +37,18 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!isRootCollection"
|
||||
label="Inherit"
|
||||
:icon="authName === 'Inherit' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'Inherit'"
|
||||
@click="
|
||||
() => {
|
||||
auth.authType = 'inherit'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
label="Basic Auth"
|
||||
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
||||
@@ -149,6 +161,17 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'inherit'" class="p-4">
|
||||
<span v-if="inheritedProperties?.auth">
|
||||
Inherited
|
||||
{{ getAuthName(inheritedProperties.auth.inheritedAuth.authType) }}
|
||||
from Parent Collection {{ inheritedProperties?.auth.parentName }}
|
||||
</span>
|
||||
<span v-else>
|
||||
Please save this request in any collection to inherit the
|
||||
authorization
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
@@ -203,6 +226,8 @@ import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { onMounted } from "vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -210,12 +235,24 @@ const colorMode = useColorMode()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppGQLAuth
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppGQLAuth): void
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isRootCollection && auth.value.authType === "inherit") {
|
||||
auth.value = {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const auth = useVModel(props, "modelValue", emit)
|
||||
|
||||
const AUTH_KEY_NAME = {
|
||||
@@ -224,12 +261,20 @@ const AUTH_KEY_NAME = {
|
||||
"oauth-2": "OAuth 2.0",
|
||||
"api-key": "API key",
|
||||
none: "None",
|
||||
inherit: "Inherit",
|
||||
} as const
|
||||
|
||||
const authType = pluckRef(auth, "authType")
|
||||
|
||||
const authName = computed(() =>
|
||||
AUTH_KEY_NAME[authType.value] ? AUTH_KEY_NAME[authType.value] : "None"
|
||||
)
|
||||
|
||||
const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
|
||||
if (!type) return "None"
|
||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||
}
|
||||
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
|
||||
const clearContent = () => {
|
||||
|
||||
@@ -77,22 +77,11 @@
|
||||
tabindex="-1"
|
||||
/>
|
||||
</span>
|
||||
<HoppSmartAutoComplete
|
||||
<SmartEnvInput
|
||||
v-model="header.key"
|
||||
:placeholder="`${t('count.header', { count: index + 1 })}`"
|
||||
:source="commonHeaders"
|
||||
:spellcheck="false"
|
||||
:value="header.key"
|
||||
autofocus
|
||||
styles="
|
||||
bg-transparent
|
||||
flex
|
||||
flex-1
|
||||
py-1
|
||||
px-4
|
||||
truncate
|
||||
"
|
||||
class="!flex flex-1"
|
||||
@input="
|
||||
:auto-complete-source="commonHeaders"
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
key: $event,
|
||||
@@ -101,17 +90,14 @@
|
||||
})
|
||||
"
|
||||
/>
|
||||
<input
|
||||
class="flex flex-1 bg-transparent px-4 py-2"
|
||||
<SmartEnvInput
|
||||
v-model="header.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:name="`value ${String(index)}`"
|
||||
:value="header.value"
|
||||
autofocus
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
key: header.key,
|
||||
value: ($event!.target! as HTMLInputElement).value,
|
||||
value: $event,
|
||||
active: header.active,
|
||||
})
|
||||
"
|
||||
@@ -156,6 +142,119 @@
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<draggable
|
||||
v-model="computedHeaders"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
ghost-class="cursor-move"
|
||||
chosen-class="bg-primaryLight"
|
||||
drag-class="cursor-grabbing"
|
||||
>
|
||||
<template #item="{ element: header, index }">
|
||||
<div
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLock"
|
||||
class="cursor-auto bg-divider text-secondaryLight opacity-25"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</span>
|
||||
<SmartEnvInput
|
||||
v-model="header.header.key"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<SmartEnvInput
|
||||
:model-value="mask(header)"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-if="header.source === 'auth'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||
:icon="masking ? IconEye : IconEyeOff"
|
||||
@click="toggleMask()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArrowUpRight"
|
||||
:title="t('request.go_to_authorization_tab')"
|
||||
class="cursor-auto text-primary hover:text-primary"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArrowUpRight"
|
||||
:title="t('request.go_to_authorization_tab')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<draggable
|
||||
v-model="inheritedProperties"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
ghost-class="cursor-move"
|
||||
chosen-class="bg-primaryLight"
|
||||
drag-class="cursor-grabbing"
|
||||
>
|
||||
<template #item="{ element: header, index }">
|
||||
<div
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLock"
|
||||
class="cursor-auto bg-divider text-secondaryLight opacity-25"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</span>
|
||||
<SmartEnvInput
|
||||
v-model="header.header.key"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<SmartEnvInput
|
||||
:model-value="
|
||||
header.source === 'auth' ? mask(header) : header.header.value
|
||||
"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="header.source === 'auth'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||
:icon="masking && header.source === 'auth' ? IconEye : IconEyeOff"
|
||||
@click="toggleMask()"
|
||||
/>
|
||||
<span v-else class="aspect-square w-[2.05rem]"></span>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconInfo"
|
||||
:title="`This header is inherited from Parent Collection ${
|
||||
header.inheritedFrom ?? ''
|
||||
}`"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<HoppSmartPlaceholder
|
||||
v-if="workingHeaders.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/add_category.svg`"
|
||||
@@ -184,7 +283,12 @@ import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right"
|
||||
import IconLock from "~icons/lucide/lock"
|
||||
import IconEye from "~icons/lucide/eye"
|
||||
import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -206,13 +310,20 @@ import { commonHeaders } from "~/helpers/headers"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import { objRemoveKey } from "~/helpers/functional/object"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppGQLHeader } from "~/helpers/graphql"
|
||||
import { throwError } from "~/helpers/functional/error"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = defineProps<{ modelValue: HoppGQLRequest }>()
|
||||
const props = defineProps<{
|
||||
modelValue: HoppGQLRequest
|
||||
isCollectionProperty?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppGQLRequest): void
|
||||
@@ -413,7 +524,11 @@ const deleteHeader = (index: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
workingHeaders.value.splice(index, 1)
|
||||
workingHeaders.value = pipe(
|
||||
workingHeaders.value,
|
||||
A.deleteAt(index),
|
||||
O.getOrElseW(() => throwError("Working Headers Deletion Out of Bounds"))
|
||||
)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
@@ -429,4 +544,151 @@ const clearContent = () => {
|
||||
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
|
||||
const getComputedAuthHeaders = (
|
||||
req?: HoppGQLRequest,
|
||||
auth?: HoppGQLRequest["auth"]
|
||||
) => {
|
||||
const request = auth ? { auth: auth ?? { authActive: false } } : req
|
||||
// If Authorization header is also being user-defined, that takes priority
|
||||
if (req && req.headers.find((h) => h.key.toLowerCase() === "authorization"))
|
||||
return []
|
||||
|
||||
if (!request) return []
|
||||
|
||||
if (!request.auth || !request.auth.authActive) return []
|
||||
|
||||
const headers: HoppGQLHeader[] = []
|
||||
|
||||
// TODO: Support a better b64 implementation than btoa ?
|
||||
if (request.auth.authType === "basic") {
|
||||
const username = request.auth.username
|
||||
const password = request.auth.password
|
||||
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||
})
|
||||
} else if (
|
||||
request.auth.authType === "bearer" ||
|
||||
request.auth.authType === "oauth-2"
|
||||
) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${request.auth.token}`,
|
||||
})
|
||||
} else if (request.auth.authType === "api-key") {
|
||||
const { key, addTo } = request.auth
|
||||
|
||||
if (addTo === "Headers" && key) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key,
|
||||
value: request.auth.value ?? "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
const getComputedHeaders = (req: HoppGQLRequest) => {
|
||||
return [
|
||||
...getComputedAuthHeaders(req).map((header) => ({
|
||||
source: "auth" as const,
|
||||
header,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
const computedHeaders = computed(() =>
|
||||
getComputedHeaders(request.value).map((header, index) => ({
|
||||
id: `header-${index}`,
|
||||
...header,
|
||||
}))
|
||||
)
|
||||
|
||||
const inheritedProperties = computed(() => {
|
||||
if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers)
|
||||
return []
|
||||
|
||||
//filter out headers that are already in the request headers
|
||||
|
||||
const inheritedHeaders = props.inheritedProperties.headers.filter(
|
||||
(header) =>
|
||||
!request.value.headers.some(
|
||||
(requestHeader) => requestHeader.key === header.inheritedHeader?.key
|
||||
)
|
||||
)
|
||||
|
||||
const headers = inheritedHeaders
|
||||
.filter(
|
||||
(header) =>
|
||||
header.inheritedHeader !== null &&
|
||||
header.inheritedHeader !== undefined &&
|
||||
header.inheritedHeader.active
|
||||
)
|
||||
.map((header, index) => ({
|
||||
inheritedFrom: props.inheritedProperties?.headers[index].parentName,
|
||||
source: "headers",
|
||||
id: `header-${index}`,
|
||||
header: {
|
||||
key: header.inheritedHeader?.key,
|
||||
value: header.inheritedHeader?.value,
|
||||
active: header.inheritedHeader?.active,
|
||||
},
|
||||
}))
|
||||
|
||||
let auth = [] as {
|
||||
inheritedFrom: string
|
||||
source: "auth"
|
||||
id: string
|
||||
header: {
|
||||
key: string
|
||||
value: string
|
||||
active: boolean
|
||||
}
|
||||
}[]
|
||||
|
||||
const computedAuthHeader = getComputedAuthHeaders(
|
||||
request.value,
|
||||
props.inheritedProperties.auth.inheritedAuth
|
||||
)[0]
|
||||
|
||||
if (
|
||||
computedAuthHeader &&
|
||||
request.value.auth.authType === "inherit" &&
|
||||
request.value.auth.authActive
|
||||
) {
|
||||
auth = [
|
||||
{
|
||||
inheritedFrom: props.inheritedProperties?.auth.parentName,
|
||||
source: "auth",
|
||||
id: `header-auth`,
|
||||
header: computedAuthHeader,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [...headers, ...auth]
|
||||
})
|
||||
|
||||
const masking = ref(true)
|
||||
|
||||
const toggleMask = () => {
|
||||
masking.value = !masking.value
|
||||
}
|
||||
|
||||
const mask = (header: any) => {
|
||||
if (header.source === "auth" && masking.value)
|
||||
return header.header.value.replace(/\S/gi, "*")
|
||||
return header.header.value
|
||||
}
|
||||
|
||||
// const changeTab = (tab: ComputedHeader["source"]) => {
|
||||
// if (tab === "auth") emit("change-tab", "authorization")
|
||||
// else emit("change-tab", "bodyParams")
|
||||
// }
|
||||
</script>
|
||||
|
||||
@@ -34,10 +34,16 @@
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="activeGQLHeadersCount === 0 ? null : `${activeGQLHeadersCount}`"
|
||||
>
|
||||
<GraphqlHeaders v-model="request" />
|
||||
<GraphqlHeaders
|
||||
v-model="request"
|
||||
:inherited-properties="inheritedProperties"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||
<GraphqlAuthorization v-model="request.auth" />
|
||||
<GraphqlAuthorization
|
||||
v-model="request.auth"
|
||||
:inherited-properties="inheritedProperties"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
<CollectionsSaveRequest
|
||||
@@ -69,6 +75,7 @@ import { useService } from "dioc/vue"
|
||||
import { InterceptorService } from "~/services/interceptor.service"
|
||||
import { editGraphqlRequest } from "~/newstore/collections"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const VALID_GQL_OPERATIONS = [
|
||||
"query",
|
||||
@@ -93,24 +100,22 @@ const props = withDefaults(
|
||||
response?: GQLResponseEvent[] | null
|
||||
optionTab?: GQLOptionTabs
|
||||
tabId: string
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>(),
|
||||
{
|
||||
response: null,
|
||||
optionTab: "query",
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(["update:modelValue", "update:response"])
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppGQLRequest): void
|
||||
(e: "update:optionTab", value: GQLOptionTabs): void
|
||||
(e: "update:response", value: GQLResponseEvent[]): void
|
||||
}>()
|
||||
|
||||
const selectedOptionTab = useVModel(props, "optionTab", emit)
|
||||
|
||||
const request = ref(props.modelValue)
|
||||
|
||||
watch(
|
||||
() => request.value,
|
||||
(newVal) => {
|
||||
emit("update:modelValue", newVal)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
const request = useVModel(props, "modelValue", emit)
|
||||
|
||||
const url = computedWithControl(
|
||||
() => tabs.currentActiveTab.value,
|
||||
@@ -131,10 +136,30 @@ const runQuery = async (
|
||||
startPageProgress()
|
||||
try {
|
||||
const runURL = clone(url.value)
|
||||
const runHeaders = clone(request.value.headers)
|
||||
const runQuery = clone(request.value.query)
|
||||
const runVariables = clone(request.value.variables)
|
||||
const runAuth = clone(request.value.auth)
|
||||
const runAuth =
|
||||
request.value.auth.authType === "inherit" && request.value.auth.authActive
|
||||
? clone(tabs.currentActiveTab.value.document.inheritedProperties?.auth)
|
||||
: clone(request.value.auth)
|
||||
|
||||
const inheritedHeaders =
|
||||
tabs.currentActiveTab.value.document.inheritedProperties?.headers.map(
|
||||
(header) => {
|
||||
if (header.inheritedHeader) {
|
||||
return header.inheritedHeader
|
||||
}
|
||||
return []
|
||||
}
|
||||
)
|
||||
|
||||
let runHeaders: HoppGQLRequest["headers"] = []
|
||||
|
||||
if (inheritedHeaders) {
|
||||
runHeaders = [...inheritedHeaders, ...clone(request.value.headers)]
|
||||
} else {
|
||||
runHeaders = clone(request.value.headers)
|
||||
}
|
||||
|
||||
await runGQLOperation({
|
||||
name: request.value.name,
|
||||
@@ -142,7 +167,7 @@ const runQuery = async (
|
||||
headers: runHeaders,
|
||||
query: runQuery,
|
||||
variables: runVariables,
|
||||
auth: runAuth,
|
||||
auth: runAuth ?? { authType: "none", authActive: false },
|
||||
operationName: definition?.name?.value,
|
||||
operationType: definition?.operation ?? "query",
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
v-model="tab.document.request"
|
||||
v-model:response="tab.document.response"
|
||||
v-model:option-tab="tab.document.optionTabPreference"
|
||||
v-model:inherited-properties="tab.document.inheritedProperties"
|
||||
:tab-id="tab.id"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
class="sticky z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
:class="{
|
||||
'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold':
|
||||
!isCollectionProperty,
|
||||
'top-propertiesPrimaryStickyFold': isCollectionProperty,
|
||||
}"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
@@ -37,6 +42,18 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!isRootCollection"
|
||||
label="Inherit"
|
||||
:icon="authName === 'Inherit' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'Inherit'"
|
||||
@click="
|
||||
() => {
|
||||
auth.authType = 'inherit'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
label="Basic Auth"
|
||||
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
||||
@@ -135,6 +152,17 @@
|
||||
<div v-if="auth.authType === 'basic'">
|
||||
<HttpAuthorizationBasic v-model="auth" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'inherit'" class="p-4">
|
||||
<span v-if="inheritedProperties?.auth">
|
||||
Inherited
|
||||
{{ getAuthName(inheritedProperties.auth.inheritedAuth.authType) }}
|
||||
from Parent Collection {{ inheritedProperties?.auth.parentName }}
|
||||
</span>
|
||||
<span v-else>
|
||||
Please save this request in any collection to inherit the
|
||||
authorization
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
@@ -181,6 +209,8 @@ import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { onMounted } from "vue"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -188,6 +218,9 @@ const colorMode = useColorMode()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuth
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -196,18 +229,34 @@ const emit = defineEmits<{
|
||||
|
||||
const auth = useVModel(props, "modelValue", emit)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isRootCollection && auth.value.authType === "inherit") {
|
||||
auth.value = {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const AUTH_KEY_NAME = {
|
||||
basic: "Basic Auth",
|
||||
bearer: "Bearer",
|
||||
"oauth-2": "OAuth 2.0",
|
||||
"api-key": "API key",
|
||||
none: "None",
|
||||
inherit: "Inherit",
|
||||
} as const
|
||||
|
||||
const authType = pluckRef(auth, "authType")
|
||||
const authName = computed(() =>
|
||||
AUTH_KEY_NAME[authType.value] ? AUTH_KEY_NAME[authType.value] : "None"
|
||||
)
|
||||
|
||||
const getAuthName = (type: HoppRESTAuth["authType"] | undefined) => {
|
||||
if (!type) return "None"
|
||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||
}
|
||||
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
|
||||
const clearContent = () => {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
class="sticky z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
:class="{
|
||||
'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold':
|
||||
!isCollectionProperty,
|
||||
'top-propertiesPrimaryStickyFold': isCollectionProperty,
|
||||
}"
|
||||
>
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.header_list") }}
|
||||
@@ -203,6 +208,61 @@
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<draggable
|
||||
v-model="inheritedProperties"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
ghost-class="cursor-move"
|
||||
chosen-class="bg-primaryLight"
|
||||
drag-class="cursor-grabbing"
|
||||
>
|
||||
<template #item="{ element: header, index }">
|
||||
<div
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLock"
|
||||
class="cursor-auto bg-divider text-secondaryLight opacity-25"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</span>
|
||||
<SmartEnvInput
|
||||
v-model="header.header.key"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<SmartEnvInput
|
||||
:model-value="
|
||||
header.source === 'auth' ? mask(header) : header.header.value
|
||||
"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
readonly
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="header.source === 'auth'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t(masking ? 'state.show' : 'state.hide')"
|
||||
:icon="masking && header.source === 'auth' ? IconEye : IconEyeOff"
|
||||
@click="toggleMask()"
|
||||
/>
|
||||
<span v-else class="aspect-square w-[2.05rem]"></span>
|
||||
<span>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconInfo"
|
||||
:title="`This header is inherited from Parent Collection ${
|
||||
header.inheritedFrom ?? ''
|
||||
}`"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<HoppSmartPlaceholder
|
||||
v-if="workingHeaders.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/add_category.svg`"
|
||||
@@ -236,6 +296,7 @@ import IconEye from "~icons/lucide/eye"
|
||||
import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import { isEqual, cloneDeep } from "lodash-es"
|
||||
@@ -264,12 +325,14 @@ import { objRemoveKey } from "~/helpers/functional/object"
|
||||
import {
|
||||
ComputedHeader,
|
||||
getComputedHeaders,
|
||||
getComputedAuthHeaders,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -288,7 +351,11 @@ const linewrapEnabled = ref(true)
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTRequest
|
||||
isCollectionProperty?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RESTOptionTabs): void
|
||||
@@ -494,6 +561,72 @@ const computedHeaders = computed(() =>
|
||||
)
|
||||
)
|
||||
|
||||
const inheritedProperties = computed(() => {
|
||||
if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers)
|
||||
return []
|
||||
|
||||
//filter out headers that are already in the request headers
|
||||
|
||||
const inheritedHeaders = props.inheritedProperties.headers.filter(
|
||||
(header) =>
|
||||
!request.value.headers.some(
|
||||
(requestHeader) => requestHeader.key === header.inheritedHeader?.key
|
||||
)
|
||||
)
|
||||
|
||||
const headers = inheritedHeaders
|
||||
.filter(
|
||||
(header) =>
|
||||
header.inheritedHeader !== null &&
|
||||
header.inheritedHeader !== undefined &&
|
||||
header.inheritedHeader.active
|
||||
)
|
||||
.map((header, index) => ({
|
||||
inheritedFrom: props.inheritedProperties?.headers[index].parentName,
|
||||
source: "headers",
|
||||
id: `header-${index}`,
|
||||
header: {
|
||||
key: header.inheritedHeader?.key,
|
||||
value: header.inheritedHeader?.value,
|
||||
active: header.inheritedHeader?.active,
|
||||
},
|
||||
}))
|
||||
|
||||
let auth = [] as {
|
||||
inheritedFrom: string
|
||||
source: "auth"
|
||||
id: string
|
||||
header: {
|
||||
key: string
|
||||
value: string
|
||||
active: boolean
|
||||
}
|
||||
}[]
|
||||
|
||||
const computedAuthHeader = getComputedAuthHeaders(
|
||||
aggregateEnvs.value,
|
||||
request.value,
|
||||
props.inheritedProperties.auth.inheritedAuth
|
||||
)[0]
|
||||
|
||||
if (
|
||||
computedAuthHeader &&
|
||||
request.value.auth.authType === "inherit" &&
|
||||
request.value.auth.authActive
|
||||
) {
|
||||
auth = [
|
||||
{
|
||||
inheritedFrom: props.inheritedProperties?.auth.parentName,
|
||||
source: "auth",
|
||||
id: `header-auth`,
|
||||
header: computedAuthHeader,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [...headers, ...auth]
|
||||
})
|
||||
|
||||
const masking = ref(true)
|
||||
|
||||
const toggleMask = () => {
|
||||
|
||||
@@ -29,14 +29,21 @@
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
>
|
||||
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||
<HttpHeaders
|
||||
v-model="request"
|
||||
:inherited-properties="inheritedProperties"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('authorization') : true"
|
||||
:id="'authorization'"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization v-model="request.auth" />
|
||||
<HttpAuthorization
|
||||
v-model="request.auth"
|
||||
:inherited-properties="inheritedProperties"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||
@@ -69,6 +76,7 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { computed } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const VALID_OPTION_TABS = [
|
||||
"params",
|
||||
@@ -89,6 +97,7 @@ const props = withDefaults(
|
||||
modelValue: HoppRESTRequest
|
||||
optionTab: RESTOptionTabs
|
||||
properties?: string[]
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>(),
|
||||
{
|
||||
optionTab: "params",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<HttpRequestOptions
|
||||
v-model="tab.document.request"
|
||||
v-model:option-tab="tab.document.optionTabPreference"
|
||||
v-model:inherited-properties="tab.document.inheritedProperties"
|
||||
/>
|
||||
</template>
|
||||
<template #secondary>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
@@ -48,7 +48,7 @@ const hasSelectedCollectionID = computed(() => {
|
||||
const myCollections = useReadonlyStream(restCollections$, [])
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "importFromMyCollection", content: HoppCollection<HoppRESTRequest>): void
|
||||
(e: "importFromMyCollection", content: HoppCollection): void
|
||||
}>()
|
||||
|
||||
const fetchCollectionFromMyCollections = async () => {
|
||||
|
||||
@@ -95,13 +95,41 @@ export function runRESTRequest$(
|
||||
return E.left("script_fail" as const)
|
||||
}
|
||||
|
||||
const effectiveRequest = getEffectiveRESTRequest(
|
||||
tab.value.document.request,
|
||||
{
|
||||
name: "Env",
|
||||
variables: combineEnvVariables(envs.right),
|
||||
}
|
||||
)
|
||||
const requestAuth =
|
||||
tab.value.document.request.auth.authType === "inherit" &&
|
||||
tab.value.document.request.auth.authActive
|
||||
? tab.value.document.inheritedProperties?.auth.inheritedAuth
|
||||
: tab.value.document.request.auth
|
||||
|
||||
let requestHeaders
|
||||
|
||||
const inheritedHeaders =
|
||||
tab.value.document.inheritedProperties?.headers.map((header) => {
|
||||
if (header.inheritedHeader) {
|
||||
return header.inheritedHeader
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
if (inheritedHeaders) {
|
||||
requestHeaders = [
|
||||
...inheritedHeaders,
|
||||
...tab.value.document.request.headers,
|
||||
]
|
||||
} else {
|
||||
requestHeaders = [...tab.value.document.request.headers]
|
||||
}
|
||||
|
||||
const finalRequest = {
|
||||
...tab.value.document.request,
|
||||
auth: requestAuth ?? { authType: "none", authActive: false },
|
||||
headers: requestHeaders,
|
||||
}
|
||||
|
||||
const effectiveRequest = getEffectiveRESTRequest(finalRequest, {
|
||||
name: "Env",
|
||||
variables: combineEnvVariables(envs.right),
|
||||
})
|
||||
|
||||
const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
|
||||
cancelFunc = cancelRun
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
mutation UpdateTeamCollection(
|
||||
$collectionID: ID!
|
||||
$newTitle: String
|
||||
$data: String
|
||||
) {
|
||||
updateTeamCollection(
|
||||
collectionID: $collectionID
|
||||
newTitle: $newTitle
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ query GetCollectionChildren($collectionID: ID!, $cursor: ID) {
|
||||
children(cursor: $cursor) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
query GetCollectionTitle($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
query GetCollectionTitleAndData($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ query GetSingleCollection($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@ query RootCollectionsOfTeam($teamID: ID!, $cursor: ID) {
|
||||
rootCollectionsOfTeam(teamID: $teamID, cursor: $cursor) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ subscription TeamCollectionAdded($teamID: ID!) {
|
||||
teamCollectionAdded(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ subscription TeamCollectionUpdated($teamID: ID!) {
|
||||
teamCollectionUpdated(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
makeCollection,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
ExportAsJsonDocument,
|
||||
GetCollectionChildrenIDsDocument,
|
||||
GetCollectionRequestsDocument,
|
||||
GetCollectionTitleDocument,
|
||||
GetCollectionTitleAndDataDocument,
|
||||
} from "./graphql"
|
||||
|
||||
export const BACKEND_PAGE_SIZE = 10
|
||||
@@ -85,16 +84,19 @@ export const getCompleteCollectionTree = (
|
||||
pipe(
|
||||
TE.Do,
|
||||
|
||||
TE.bind("title", () =>
|
||||
TE.bind("titleAndData", () =>
|
||||
pipe(
|
||||
() =>
|
||||
runGQLQuery({
|
||||
query: GetCollectionTitleDocument,
|
||||
query: GetCollectionTitleAndDataDocument,
|
||||
variables: {
|
||||
collectionID: collID,
|
||||
},
|
||||
}),
|
||||
TE.map((x) => x.collection!.title)
|
||||
TE.map((result) => ({
|
||||
title: result.collection!.title,
|
||||
data: result.collection!.data,
|
||||
}))
|
||||
)
|
||||
),
|
||||
TE.bind("children", () =>
|
||||
@@ -108,24 +110,36 @@ export const getCompleteCollectionTree = (
|
||||
TE.bind("requests", () => () => getCollectionRequests(collID)),
|
||||
|
||||
TE.map(
|
||||
({ title, children, requests }) =>
|
||||
({ titleAndData, children, requests }) =>
|
||||
<TeamCollection>{
|
||||
id: collID,
|
||||
children,
|
||||
requests,
|
||||
title,
|
||||
title: titleAndData.title,
|
||||
data: titleAndData.data,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export const teamCollToHoppRESTColl = (
|
||||
coll: TeamCollection
|
||||
): HoppCollection<HoppRESTRequest> =>
|
||||
makeCollection({
|
||||
): HoppCollection => {
|
||||
const data =
|
||||
coll.data && coll.data !== "null"
|
||||
? JSON.parse(coll.data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return makeCollection({
|
||||
name: coll.title,
|
||||
folders: coll.children?.map(teamCollToHoppRESTColl) ?? [],
|
||||
requests: coll.requests?.map((x) => x.request) ?? [],
|
||||
auth: data.auth ?? { authType: "inherit", authActive: true },
|
||||
headers: data.headers ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON string of all the collection of the specified team
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
UpdateCollectionOrderDocument,
|
||||
UpdateCollectionOrderMutation,
|
||||
UpdateCollectionOrderMutationVariables,
|
||||
UpdateTeamCollectionDocument,
|
||||
UpdateTeamCollectionMutation,
|
||||
UpdateTeamCollectionMutationVariables,
|
||||
} from "../graphql"
|
||||
|
||||
type CreateNewRootCollectionError = "team_coll/short_title"
|
||||
@@ -122,3 +125,18 @@ export const importJSONToTeam = (collectionJSON: string, teamID: string) =>
|
||||
teamID,
|
||||
}
|
||||
)
|
||||
|
||||
export const updateTeamCollection = (
|
||||
collectionID: string,
|
||||
data?: string,
|
||||
newTitle?: string
|
||||
) =>
|
||||
runMutation<
|
||||
UpdateTeamCollectionMutation,
|
||||
UpdateTeamCollectionMutationVariables,
|
||||
""
|
||||
>(UpdateTeamCollectionDocument, {
|
||||
collectionID,
|
||||
data,
|
||||
newTitle,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { getAffectedIndexes } from "./affectedIndex"
|
||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||
import { runGQLQuery } from "../backend/GQLClient"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
/**
|
||||
* Resolve save context on reorder
|
||||
@@ -108,6 +110,135 @@ export function updateSaveContextForAffectedRequests(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check the new folder path is close to the save context folder path or not
|
||||
* @param folderPathCurrent The path saved as the inherited path in the inherited properties
|
||||
* @param newFolderPath The incomming path
|
||||
* @param saveContextPath The save context of the request
|
||||
* @returns The path which is close to saveContext.folderPath
|
||||
*/
|
||||
function folderPathCloseToSaveContext(
|
||||
folderPathCurrent: string | undefined,
|
||||
newFolderPath: string,
|
||||
saveContextPath: string
|
||||
) {
|
||||
if (!folderPathCurrent) return newFolderPath
|
||||
const folderPathCurrentArray = folderPathCurrent.split("/")
|
||||
const newFolderPathArray = newFolderPath.split("/")
|
||||
|
||||
const saveContextFolderPathArray = saveContextPath.split("/")
|
||||
|
||||
let folderPathCurrentMatch = 0
|
||||
|
||||
for (let i = 0; i < folderPathCurrentArray.length; i++) {
|
||||
if (folderPathCurrentArray[i] === saveContextFolderPathArray[i]) {
|
||||
folderPathCurrentMatch++
|
||||
}
|
||||
}
|
||||
|
||||
let newFolderPathMatch = 0
|
||||
|
||||
for (let i = 0; i < newFolderPathArray.length; i++) {
|
||||
if (newFolderPathArray[i] === saveContextFolderPathArray[i]) {
|
||||
newFolderPathMatch++
|
||||
}
|
||||
}
|
||||
|
||||
if (folderPathCurrentMatch > newFolderPathMatch) {
|
||||
return folderPathCurrent
|
||||
}
|
||||
return newFolderPath
|
||||
}
|
||||
|
||||
export function updateInheritedPropertiesForAffectedRequests(
|
||||
path: string,
|
||||
inheritedProperties: HoppInheritedProperty,
|
||||
type: "rest" | "graphql",
|
||||
workspace: "personal" | "team" = "personal"
|
||||
) {
|
||||
const tabService =
|
||||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||
|
||||
let tabs
|
||||
if (workspace === "personal") {
|
||||
tabs = tabService.getTabsRefTo((tab) => {
|
||||
return (
|
||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||
tab.document.saveContext.folderPath.startsWith(path)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
tabs = tabService.getTabsRefTo((tab) => {
|
||||
return (
|
||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
||||
tab.document.saveContext.collectionID?.startsWith(path)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const tabsEffectedByAuth = tabs.filter((tab) => {
|
||||
if (workspace === "personal") {
|
||||
return (
|
||||
tab.value.document.saveContext?.originLocation === "user-collection" &&
|
||||
tab.value.document.saveContext.folderPath.startsWith(path) &&
|
||||
path ===
|
||||
folderPathCloseToSaveContext(
|
||||
tab.value.document.inheritedProperties?.auth.parentID,
|
||||
path,
|
||||
tab.value.document.saveContext.folderPath
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
tab.value.document.saveContext?.originLocation === "team-collection" &&
|
||||
tab.value.document.saveContext.collectionID?.startsWith(path) &&
|
||||
path ===
|
||||
folderPathCloseToSaveContext(
|
||||
tab.value.document.inheritedProperties?.auth.parentID,
|
||||
path,
|
||||
tab.value.document.saveContext.collectionID
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const tabsEffectedByHeaders = tabs.filter((tab) => {
|
||||
return (
|
||||
tab.value.document.inheritedProperties &&
|
||||
tab.value.document.inheritedProperties.headers.some(
|
||||
(header) => header.parentID === path
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
for (const tab of tabsEffectedByAuth) {
|
||||
tab.value.document.inheritedProperties = inheritedProperties
|
||||
}
|
||||
|
||||
for (const tab of tabsEffectedByHeaders) {
|
||||
const headers = tab.value.document.inheritedProperties?.headers.map(
|
||||
(header) => {
|
||||
if (header.parentID === path) {
|
||||
return {
|
||||
...header,
|
||||
inheritedHeader: inheritedProperties.headers.find(
|
||||
(inheritedHeader) =>
|
||||
inheritedHeader.inheritedHeader?.key ===
|
||||
header.inheritedHeader?.key
|
||||
)?.inheritedHeader,
|
||||
}
|
||||
}
|
||||
return header
|
||||
}
|
||||
)
|
||||
|
||||
tab.value.document.inheritedProperties = {
|
||||
...tab.value.document.inheritedProperties,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||
const tabService = getService(RESTTabService)
|
||||
const tabs = tabService.getTabsRefTo((tab) => {
|
||||
@@ -152,9 +283,9 @@ export async function resetTeamRequestsContext() {
|
||||
}
|
||||
|
||||
export function getFoldersByPath(
|
||||
collections: HoppCollection<HoppRESTRequest>[],
|
||||
collections: HoppCollection[],
|
||||
path: string
|
||||
): HoppCollection<HoppRESTRequest>[] {
|
||||
): HoppCollection[] {
|
||||
if (!path) return collections
|
||||
|
||||
// path will be like this "0/0/1" these are the indexes of the folders
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { getAffectedIndexes } from "./affectedIndex"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { getService } from "~/modules/dioc"
|
||||
@@ -53,9 +57,9 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
||||
}
|
||||
|
||||
export function getRequestsByPath(
|
||||
collections: HoppCollection<HoppRESTRequest>[],
|
||||
collections: HoppCollection[],
|
||||
path: string
|
||||
): HoppRESTRequest[] {
|
||||
): HoppRESTRequest[] | HoppGQLRequest[] {
|
||||
// path will be like this "0/0/1" these are the indexes of the folders
|
||||
const pathArray = path.split("/").map((index) => parseInt(index))
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const getDefaultGQLRequest = (): HoppGQLRequest => ({
|
||||
}`,
|
||||
query: DEFAULT_QUERY,
|
||||
auth: {
|
||||
authType: "none",
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { GQLResponseEvent } from "./connection"
|
||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
export type HoppGQLSaveContext =
|
||||
| {
|
||||
@@ -73,4 +74,10 @@ export type HoppGQLDocument = {
|
||||
* Options tab preference for the current tab's document
|
||||
*/
|
||||
optionTabPreference?: GQLOptionTabs
|
||||
|
||||
/**
|
||||
* The inherited properties from the parent collection
|
||||
* (if any)
|
||||
*/
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
export const gqlCollectionsExporter = (
|
||||
gqlCollections: HoppCollection<HoppGQLRequest>[]
|
||||
) => {
|
||||
export const gqlCollectionsExporter = (gqlCollections: HoppCollection[]) => {
|
||||
return JSON.stringify(gqlCollections, null, 2)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
export const myCollectionsExporter = (
|
||||
myCollections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
export const myCollectionsExporter = (myCollections: HoppCollection[]) => {
|
||||
return JSON.stringify(myCollections, null, 2)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ import { pipe, flow } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import {
|
||||
translateToNewRESTCollection,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
|
||||
import { isPlainObject as _isPlainObject } from "lodash-es"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
import { translateToNewGQLCollection } from "@hoppscotch/data"
|
||||
|
||||
export const hoppRESTImporter = (content: string) =>
|
||||
pipe(
|
||||
@@ -33,12 +30,10 @@ const isPlainObject = (value: any): value is object => _isPlainObject(value)
|
||||
|
||||
/**
|
||||
* checks if a collection matches the schema for a hoppscotch collection.
|
||||
* as of now we are only checking if the collection has a "v" key in it.
|
||||
* here 2 is the latest version of the schema.
|
||||
*/
|
||||
const isValidCollection = (
|
||||
collection: unknown
|
||||
): collection is HoppCollection<HoppRESTRequest> =>
|
||||
isPlainObject(collection) && "v" in collection
|
||||
const isValidCollection = (collection: unknown): collection is HoppCollection =>
|
||||
isPlainObject(collection) && "v" in collection && collection.v === 2
|
||||
|
||||
/**
|
||||
* checks if a collection is a valid hoppscotch collection.
|
||||
@@ -56,3 +51,29 @@ const validateCollection = (collection: unknown) => {
|
||||
*/
|
||||
const makeCollectionsArray = (collections: unknown | unknown[]): unknown[] =>
|
||||
Array.isArray(collections) ? collections : [collections]
|
||||
|
||||
export const hoppGQLImporter = (content: string) =>
|
||||
pipe(
|
||||
safeParseJSON(content),
|
||||
O.chain(
|
||||
flow(
|
||||
makeCollectionsArray,
|
||||
RA.map(validateGQLCollection),
|
||||
O.sequenceArray,
|
||||
O.map(RA.toArray)
|
||||
)
|
||||
),
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param collection the collection to validate
|
||||
* @returns the collection if it is valid, else a translated version of the collection
|
||||
*/
|
||||
export const validateGQLCollection = (collection: unknown) => {
|
||||
if (isValidCollection(collection)) {
|
||||
return O.some(collection)
|
||||
}
|
||||
return O.some(translateToNewGQLCollection(collection))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
// TODO: add zod validation
|
||||
export const hoppGqlCollectionsImporter = (
|
||||
content: string
|
||||
): E.Either<"INVALID_JSON", HoppCollection<HoppGQLRequest>[]> => {
|
||||
): E.Either<"INVALID_JSON", HoppCollection[]> => {
|
||||
return E.tryCatch(
|
||||
() => JSON.parse(content) as HoppCollection<HoppGQLRequest>[],
|
||||
() => JSON.parse(content) as HoppCollection[],
|
||||
() => "INVALID_JSON"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ const getHoppRequest = (req: InsomniaRequestResource): HoppRESTRequest =>
|
||||
const getHoppFolder = (
|
||||
folderRes: InsomniaFolderResource,
|
||||
resources: InsomniaResource[]
|
||||
): HoppCollection<HoppRESTRequest> =>
|
||||
): HoppCollection =>
|
||||
makeCollection({
|
||||
name: folderRes.name ?? "",
|
||||
folders: getFoldersIn(folderRes, resources).map((f) =>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
HoppRESTReqBody,
|
||||
HoppRESTRequest,
|
||||
knownContentTypes,
|
||||
makeRESTRequest,
|
||||
HoppCollection,
|
||||
@@ -581,7 +580,7 @@ const convertPathToHoppReqs = (
|
||||
|
||||
const convertOpenApiDocToHopp = (
|
||||
doc: OpenAPI.Document
|
||||
): TE.TaskEither<never, HoppCollection<HoppRESTRequest>[]> => {
|
||||
): TE.TaskEither<never, HoppCollection[]> => {
|
||||
const name = doc.info.title
|
||||
|
||||
const paths = Object.entries(doc.paths ?? {})
|
||||
@@ -589,7 +588,7 @@ const convertOpenApiDocToHopp = (
|
||||
.flat()
|
||||
|
||||
return TE.of([
|
||||
makeCollection<HoppRESTRequest>({
|
||||
makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: paths,
|
||||
|
||||
@@ -283,7 +283,7 @@ const getHoppRequest = (item: Item): HoppRESTRequest => {
|
||||
})
|
||||
}
|
||||
|
||||
const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection<HoppRESTRequest> =>
|
||||
const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection =>
|
||||
makeCollection({
|
||||
name: ig.name,
|
||||
folders: pipe(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||
import { HoppTestResult } from "../types/HoppTestResult"
|
||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
export type HoppRESTSaveContext =
|
||||
| {
|
||||
@@ -80,4 +81,10 @@ export type HoppRESTDocument = {
|
||||
* Options tab preference for the current tab's document
|
||||
*/
|
||||
optionTabPreference?: RESTOptionTabs
|
||||
|
||||
/**
|
||||
* The inherited properties from the parent collection
|
||||
* (if any)
|
||||
*/
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ export interface TeamCollection {
|
||||
title: string
|
||||
children: TeamCollection[] | null
|
||||
requests: TeamRequest[] | null
|
||||
data: string | null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
import { BehaviorSubject, Subscription } from "rxjs"
|
||||
import { translateToNewRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { pull, remove } from "lodash-es"
|
||||
import { Subscription as WSubscription } from "wonka"
|
||||
import { runGQLQuery, runGQLSubscription } from "../backend/GQLClient"
|
||||
@@ -21,6 +25,7 @@ import {
|
||||
TeamRequestOrderUpdatedDocument,
|
||||
TeamCollectionOrderUpdatedDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
const TEAMS_BACKEND_PAGE_SIZE = 10
|
||||
|
||||
@@ -542,6 +547,7 @@ export default class NewTeamCollectionAdapter {
|
||||
children: null,
|
||||
requests: null,
|
||||
title: title,
|
||||
data: null,
|
||||
},
|
||||
parentID ?? null
|
||||
)
|
||||
@@ -693,6 +699,7 @@ export default class NewTeamCollectionAdapter {
|
||||
children: null,
|
||||
requests: null,
|
||||
title: result.right.teamCollectionAdded.title,
|
||||
data: result.right.teamCollectionAdded.data ?? null,
|
||||
},
|
||||
result.right.teamCollectionAdded.parent?.id ?? null
|
||||
)
|
||||
@@ -715,6 +722,7 @@ export default class NewTeamCollectionAdapter {
|
||||
this.updateCollection({
|
||||
id: result.right.teamCollectionUpdated.id,
|
||||
title: result.right.teamCollectionUpdated.title,
|
||||
data: result.right.teamCollectionUpdated.data,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -931,6 +939,7 @@ export default class NewTeamCollectionAdapter {
|
||||
<TeamCollection>{
|
||||
id: el.id,
|
||||
title: el.title,
|
||||
data: el.data,
|
||||
children: null,
|
||||
requests: null,
|
||||
}
|
||||
@@ -1024,4 +1033,104 @@ export default class NewTeamCollectionAdapter {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public cascadeParentCollectionForHeaderAuth(folderPath: string) {
|
||||
let auth: HoppInheritedProperty["auth"] = {
|
||||
parentID: folderPath ?? "",
|
||||
parentName: "",
|
||||
inheritedAuth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
}
|
||||
const headers: HoppInheritedProperty["headers"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers }
|
||||
|
||||
const path = folderPath.split("/")
|
||||
|
||||
// Check if the path is empty or invalid
|
||||
if (!path || path.length === 0) {
|
||||
console.error("Invalid path:", folderPath)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
// Loop through the path and get the last parent folder with authType other than 'inherit'
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const parentFolder = findCollInTree(this.collections$.value, path[i])
|
||||
|
||||
// Check if parentFolder is undefined or null
|
||||
if (!parentFolder) {
|
||||
console.error("Parent folder not found for path:", path)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
const data: {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
} = parentFolder.data
|
||||
? JSON.parse(parentFolder.data)
|
||||
: {
|
||||
auth: null,
|
||||
headers: null,
|
||||
}
|
||||
|
||||
if (!data.auth) {
|
||||
data.auth = {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
}
|
||||
auth.parentID = [...path.slice(0, i + 1)].join("/")
|
||||
auth.parentName = parentFolder.title
|
||||
}
|
||||
|
||||
if (!data.headers) data.headers = []
|
||||
|
||||
const parentFolderAuth = data.auth
|
||||
const parentFolderHeaders = data.headers
|
||||
|
||||
if (parentFolderAuth?.authType === "inherit" && path.length === 1) {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: auth.inheritedAuth,
|
||||
}
|
||||
}
|
||||
|
||||
if (parentFolderAuth?.authType !== "inherit") {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: parentFolderAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// Update headers, overwriting duplicates by key
|
||||
if (parentFolderHeaders) {
|
||||
const activeHeaders = parentFolderHeaders.filter((h) => h.active)
|
||||
activeHeaders.forEach((header) => {
|
||||
const index = headers.findIndex(
|
||||
(h) => h.inheritedHeader?.key === header.key
|
||||
)
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
if (index !== -1) {
|
||||
// Replace the existing header with the same key
|
||||
headers[index] = {
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedHeader: header,
|
||||
}
|
||||
} else {
|
||||
headers.push({
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedHeader: header,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { auth, headers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
GQLHeader,
|
||||
HoppGQLAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTAuth,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
export type HoppInheritedProperty = {
|
||||
auth: {
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedAuth: HoppRESTAuth | HoppGQLAuth
|
||||
}
|
||||
headers: {
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||
}[]
|
||||
}
|
||||
@@ -42,22 +42,26 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
* @param envVars Currently active environment variables
|
||||
* @returns The list of headers
|
||||
*/
|
||||
const getComputedAuthHeaders = (
|
||||
req: HoppRESTRequest,
|
||||
envVars: Environment["variables"]
|
||||
export const getComputedAuthHeaders = (
|
||||
envVars: Environment["variables"],
|
||||
req?: HoppRESTRequest,
|
||||
auth?: HoppRESTRequest["auth"]
|
||||
) => {
|
||||
const request = auth ? { auth: auth ?? { authActive: false } } : req
|
||||
// If Authorization header is also being user-defined, that takes priority
|
||||
if (req.headers.find((h) => h.key.toLowerCase() === "authorization"))
|
||||
if (req && req.headers.find((h) => h.key.toLowerCase() === "authorization"))
|
||||
return []
|
||||
|
||||
if (!req.auth.authActive) return []
|
||||
if (!request) return []
|
||||
|
||||
if (!request.auth || !request.auth.authActive) return []
|
||||
|
||||
const headers: HoppRESTHeader[] = []
|
||||
|
||||
// TODO: Support a better b64 implementation than btoa ?
|
||||
if (req.auth.authType === "basic") {
|
||||
const username = parseTemplateString(req.auth.username, envVars)
|
||||
const password = parseTemplateString(req.auth.password, envVars)
|
||||
if (request.auth.authType === "basic") {
|
||||
const username = parseTemplateString(request.auth.username, envVars)
|
||||
const password = parseTemplateString(request.auth.password, envVars)
|
||||
|
||||
headers.push({
|
||||
active: true,
|
||||
@@ -65,22 +69,21 @@ const getComputedAuthHeaders = (
|
||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||
})
|
||||
} else if (
|
||||
req.auth.authType === "bearer" ||
|
||||
req.auth.authType === "oauth-2"
|
||||
request.auth.authType === "bearer" ||
|
||||
request.auth.authType === "oauth-2"
|
||||
) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${parseTemplateString(req.auth.token, envVars)}`,
|
||||
value: `Bearer ${parseTemplateString(request.auth.token, envVars)}`,
|
||||
})
|
||||
} else if (req.auth.authType === "api-key") {
|
||||
const { key, value, addTo } = req.auth
|
||||
|
||||
if (addTo === "Headers") {
|
||||
} else if (request.auth.authType === "api-key") {
|
||||
const { key, addTo } = request.auth
|
||||
if (addTo === "Headers" && key) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: parseTemplateString(key, envVars),
|
||||
value: parseTemplateString(value, envVars),
|
||||
value: parseTemplateString(request.auth.value ?? "", envVars),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -131,16 +134,18 @@ export type ComputedHeader = {
|
||||
export const getComputedHeaders = (
|
||||
req: HoppRESTRequest,
|
||||
envVars: Environment["variables"]
|
||||
): ComputedHeader[] => [
|
||||
...getComputedAuthHeaders(req, envVars).map((header) => ({
|
||||
source: "auth" as const,
|
||||
header,
|
||||
})),
|
||||
...getComputedBodyHeaders(req).map((header) => ({
|
||||
source: "body" as const,
|
||||
header,
|
||||
})),
|
||||
]
|
||||
): ComputedHeader[] => {
|
||||
return [
|
||||
...getComputedAuthHeaders(envVars, req).map((header) => ({
|
||||
source: "auth" as const,
|
||||
header,
|
||||
})),
|
||||
...getComputedBodyHeaders(req).map((header) => ({
|
||||
source: "body" as const,
|
||||
header,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
export type ComputedParam = {
|
||||
source: "auth"
|
||||
@@ -160,7 +165,7 @@ export const getComputedParams = (
|
||||
): ComputedParam[] => {
|
||||
// When this gets complex, its best to split this function off (like with getComputedHeaders)
|
||||
// API-key auth can be added to query params
|
||||
if (!req.auth.authActive) return []
|
||||
if (!req.auth || !req.auth.authActive) return []
|
||||
if (req.auth.authType !== "api-key") return []
|
||||
if (req.auth.addTo !== "Query params") return []
|
||||
|
||||
|
||||
@@ -10,23 +10,34 @@ import { cloneDeep } from "lodash-es"
|
||||
import { resolveSaveContextOnRequestReorder } from "~/helpers/collection/request"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
const defaultRESTCollectionState = {
|
||||
state: [
|
||||
makeCollection<HoppRESTRequest>({
|
||||
makeCollection({
|
||||
name: "My Collection",
|
||||
folders: [],
|
||||
requests: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
headers: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const defaultGraphqlCollectionState = {
|
||||
state: [
|
||||
makeCollection<HoppGQLRequest>({
|
||||
makeCollection({
|
||||
name: "My GraphQL Collection",
|
||||
folders: [],
|
||||
requests: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
headers: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
@@ -39,7 +50,7 @@ type GraphqlCollectionStoreType = typeof defaultGraphqlCollectionState
|
||||
* Not removing this behaviour because i'm not sure if we utilize this behaviour anywhere and i found this on a tight time crunch.
|
||||
*/
|
||||
export function navigateToFolderWithIndexPath(
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
collections: HoppCollection[],
|
||||
indexPaths: number[]
|
||||
) {
|
||||
if (indexPaths.length === 0) return null
|
||||
@@ -52,6 +63,94 @@ export function navigateToFolderWithIndexPath(
|
||||
return target !== undefined ? target : null
|
||||
}
|
||||
|
||||
export function cascadeParentCollectionForHeaderAuth(
|
||||
folderPath: string | undefined,
|
||||
type: "rest" | "graphql"
|
||||
) {
|
||||
const collectionStore =
|
||||
type === "rest" ? restCollectionStore : graphqlCollectionStore
|
||||
|
||||
let auth: HoppInheritedProperty["auth"] = {
|
||||
parentID: folderPath ?? "",
|
||||
parentName: "",
|
||||
inheritedAuth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
}
|
||||
const headers: HoppInheritedProperty["headers"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers }
|
||||
|
||||
const path = folderPath.split("/").map((i) => parseInt(i))
|
||||
|
||||
// Check if the path is empty or invalid
|
||||
if (!path || path.length === 0) {
|
||||
console.error("Invalid path:", folderPath)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
// Loop through the path and get the last parent folder with authType other than 'inherit'
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const parentFolder = navigateToFolderWithIndexPath(
|
||||
collectionStore.value.state,
|
||||
[...path.slice(0, i + 1)] // Create a copy of the path array
|
||||
)
|
||||
|
||||
// Check if parentFolder is undefined or null
|
||||
if (!parentFolder) {
|
||||
console.error("Parent folder not found for path:", path)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
const parentFolderAuth = parentFolder.auth
|
||||
const parentFolderHeaders = parentFolder.headers
|
||||
// check if the parent folder has authType 'inherit' and if it is the root folder
|
||||
if (parentFolderAuth?.authType === "inherit" && path.length === 1) {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.name,
|
||||
inheritedAuth: auth.inheritedAuth,
|
||||
}
|
||||
}
|
||||
|
||||
if (parentFolderAuth?.authType !== "inherit") {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.name,
|
||||
inheritedAuth: parentFolderAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// Update headers, overwriting duplicates by key
|
||||
if (parentFolderHeaders) {
|
||||
const activeHeaders = parentFolderHeaders.filter((h) => h.active)
|
||||
activeHeaders.forEach((header) => {
|
||||
const index = headers.findIndex(
|
||||
(h) => h.inheritedHeader?.key === header.key
|
||||
)
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
if (index !== -1) {
|
||||
// Replace the existing header with the same key
|
||||
headers[index] = {
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedHeader: header,
|
||||
}
|
||||
} else {
|
||||
headers.push({
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.name,
|
||||
inheritedHeader: header,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
function reorderItems(array: unknown[], from: number, to: number) {
|
||||
const item = array.splice(from, 1)[0]
|
||||
if (from < to) {
|
||||
@@ -64,7 +163,7 @@ function reorderItems(array: unknown[], from: number, to: number) {
|
||||
const restCollectionDispatchers = defineDispatchers({
|
||||
setCollections(
|
||||
_: RESTCollectionStoreType,
|
||||
{ entries }: { entries: HoppCollection<HoppRESTRequest>[] }
|
||||
{ entries }: { entries: HoppCollection[] }
|
||||
) {
|
||||
return {
|
||||
state: entries,
|
||||
@@ -73,7 +172,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
|
||||
appendCollections(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ entries }: { entries: HoppCollection<HoppRESTRequest>[] }
|
||||
{ entries }: { entries: HoppCollection[] }
|
||||
) {
|
||||
return {
|
||||
state: [...state, ...entries],
|
||||
@@ -82,7 +181,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
|
||||
addCollection(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ collection }: { collection: HoppCollection<any> }
|
||||
{ collection }: { collection: HoppCollection }
|
||||
) {
|
||||
return {
|
||||
state: [...state, collection],
|
||||
@@ -112,7 +211,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
partialCollection,
|
||||
}: {
|
||||
collectionIndex: number
|
||||
partialCollection: Partial<HoppCollection<any>>
|
||||
partialCollection: Partial<HoppCollection>
|
||||
}
|
||||
) {
|
||||
return {
|
||||
@@ -128,10 +227,15 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ name, path }: { name: string; path: string }
|
||||
) {
|
||||
const newFolder: HoppCollection<HoppRESTRequest> = makeCollection({
|
||||
const newFolder: HoppCollection = makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
})
|
||||
|
||||
const newState = state
|
||||
@@ -158,7 +262,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
folder,
|
||||
}: {
|
||||
path: string
|
||||
folder: Partial<HoppCollection<HoppRESTRequest>>
|
||||
folder: Partial<HoppCollection>
|
||||
}
|
||||
) {
|
||||
const newState = state
|
||||
@@ -249,7 +353,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
}
|
||||
|
||||
const theFolder = containingFolder.folders.splice(folderIndex, 1)
|
||||
newState.push(theFolder[0] as HoppCollection<HoppRESTRequest>)
|
||||
newState.push(theFolder[0] as HoppCollection)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
@@ -612,7 +716,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
type: "collection" | "request"
|
||||
}
|
||||
) {
|
||||
const after = removeDuplicateCollectionsFromPath<HoppRESTRequest>(
|
||||
const after = removeDuplicateCollectionsFromPath(
|
||||
id,
|
||||
collectionPath,
|
||||
state,
|
||||
@@ -628,7 +732,7 @@ const restCollectionDispatchers = defineDispatchers({
|
||||
const gqlCollectionDispatchers = defineDispatchers({
|
||||
setCollections(
|
||||
_: GraphqlCollectionStoreType,
|
||||
{ entries }: { entries: HoppCollection<any>[] }
|
||||
{ entries }: { entries: HoppCollection[] }
|
||||
) {
|
||||
return {
|
||||
state: entries,
|
||||
@@ -637,7 +741,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
|
||||
appendCollections(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ entries }: { entries: HoppCollection<any>[] }
|
||||
{ entries }: { entries: HoppCollection[] }
|
||||
) {
|
||||
return {
|
||||
state: [...state, ...entries],
|
||||
@@ -646,7 +750,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
|
||||
addCollection(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ collection }: { collection: HoppCollection<any> }
|
||||
{ collection }: { collection: HoppCollection }
|
||||
) {
|
||||
return {
|
||||
state: [...state, collection],
|
||||
@@ -673,7 +777,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
{
|
||||
collectionIndex,
|
||||
collection,
|
||||
}: { collectionIndex: number; collection: Partial<HoppCollection<any>> }
|
||||
}: { collectionIndex: number; collection: Partial<HoppCollection> }
|
||||
) {
|
||||
return {
|
||||
state: state.map((col, index) =>
|
||||
@@ -686,12 +790,16 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ name, path }: { name: string; path: string }
|
||||
) {
|
||||
const newFolder: HoppCollection<HoppGQLRequest> = makeCollection({
|
||||
const newFolder: HoppCollection = makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
})
|
||||
|
||||
const newState = state
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
@@ -711,10 +819,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
|
||||
editFolder(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{
|
||||
path,
|
||||
folder,
|
||||
}: { path: string; folder: Partial<HoppCollection<HoppGQLRequest>> }
|
||||
{ path, folder }: { path: string; folder: Partial<HoppCollection> }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
@@ -913,7 +1018,7 @@ const gqlCollectionDispatchers = defineDispatchers({
|
||||
type: "collection" | "request"
|
||||
}
|
||||
) {
|
||||
const after = removeDuplicateCollectionsFromPath<HoppGQLRequest>(
|
||||
const after = removeDuplicateCollectionsFromPath(
|
||||
id,
|
||||
collectionPath,
|
||||
state,
|
||||
@@ -936,7 +1041,7 @@ export const graphqlCollectionStore = new DispatchingStore(
|
||||
gqlCollectionDispatchers
|
||||
)
|
||||
|
||||
export function setRESTCollections(entries: HoppCollection<HoppRESTRequest>[]) {
|
||||
export function setRESTCollections(entries: HoppCollection[]) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "setCollections",
|
||||
payload: {
|
||||
@@ -953,9 +1058,7 @@ export const graphqlCollections$ = graphqlCollectionStore.subject$.pipe(
|
||||
pluck("state")
|
||||
)
|
||||
|
||||
export function appendRESTCollections(
|
||||
entries: HoppCollection<HoppRESTRequest>[]
|
||||
) {
|
||||
export function appendRESTCollections(entries: HoppCollection[]) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "appendCollections",
|
||||
payload: {
|
||||
@@ -964,7 +1067,7 @@ export function appendRESTCollections(
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTCollection(collection: HoppCollection<HoppRESTRequest>) {
|
||||
export function addRESTCollection(collection: HoppCollection) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "addCollection",
|
||||
payload: {
|
||||
@@ -992,7 +1095,7 @@ export function getRESTCollection(collectionIndex: number) {
|
||||
|
||||
export function editRESTCollection(
|
||||
collectionIndex: number,
|
||||
partialCollection: Partial<HoppCollection<HoppRESTRequest>>
|
||||
partialCollection: Partial<HoppCollection>
|
||||
) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "editCollection",
|
||||
@@ -1013,10 +1116,7 @@ export function addRESTFolder(name: string, path: string) {
|
||||
})
|
||||
}
|
||||
|
||||
export function editRESTFolder(
|
||||
path: string,
|
||||
folder: Partial<HoppCollection<HoppRESTRequest>>
|
||||
) {
|
||||
export function editRESTFolder(path: string, folder: Partial<HoppCollection>) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "editFolder",
|
||||
payload: {
|
||||
@@ -1160,9 +1260,7 @@ export function updateRESTCollectionOrder(
|
||||
})
|
||||
}
|
||||
|
||||
export function setGraphqlCollections(
|
||||
entries: HoppCollection<HoppGQLRequest>[]
|
||||
) {
|
||||
export function setGraphqlCollections(entries: HoppCollection[]) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "setCollections",
|
||||
payload: {
|
||||
@@ -1171,9 +1269,7 @@ export function setGraphqlCollections(
|
||||
})
|
||||
}
|
||||
|
||||
export function appendGraphqlCollections(
|
||||
entries: HoppCollection<HoppGQLRequest>[]
|
||||
) {
|
||||
export function appendGraphqlCollections(entries: HoppCollection[]) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "appendCollections",
|
||||
payload: {
|
||||
@@ -1182,9 +1278,7 @@ export function appendGraphqlCollections(
|
||||
})
|
||||
}
|
||||
|
||||
export function addGraphqlCollection(
|
||||
collection: HoppCollection<HoppGQLRequest>
|
||||
) {
|
||||
export function addGraphqlCollection(collection: HoppCollection) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "addCollection",
|
||||
payload: {
|
||||
@@ -1208,7 +1302,7 @@ export function removeGraphqlCollection(
|
||||
|
||||
export function editGraphqlCollection(
|
||||
collectionIndex: number,
|
||||
collection: Partial<HoppCollection<HoppGQLRequest>>
|
||||
collection: Partial<HoppCollection>
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "editCollection",
|
||||
@@ -1231,7 +1325,7 @@ export function addGraphqlFolder(name: string, path: string) {
|
||||
|
||||
export function editGraphqlFolder(
|
||||
path: string,
|
||||
folder: Partial<HoppCollection<HoppGQLRequest>>
|
||||
folder: Partial<HoppCollection>
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "editFolder",
|
||||
@@ -1322,14 +1416,12 @@ export function moveGraphqlRequest(
|
||||
})
|
||||
}
|
||||
|
||||
function removeDuplicateCollectionsFromPath<
|
||||
T extends HoppRESTRequest | HoppGQLRequest,
|
||||
>(
|
||||
function removeDuplicateCollectionsFromPath(
|
||||
idToRemove: string,
|
||||
collectionPath: string | null,
|
||||
collections: HoppCollection<T>[],
|
||||
collections: HoppCollection[],
|
||||
type: "collection" | "request"
|
||||
): HoppCollection<T>[] {
|
||||
): HoppCollection[] {
|
||||
const indexes = collectionPath?.split("/").map((x) => parseInt(x))
|
||||
indexes && indexes.pop()
|
||||
const parentPath = indexes?.join("/")
|
||||
|
||||
@@ -12,7 +12,7 @@ import { onMounted } from "vue"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { HoppRESTRequest, HoppCollection } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { appendRESTCollections } from "~/newstore/collections"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
@@ -110,9 +110,7 @@ const handleImportFailure = (error: ImportCollectionsError) => {
|
||||
toast.error(t(IMPORT_ERROR_MAP[error]).toString())
|
||||
}
|
||||
|
||||
const handleImportSuccess = (
|
||||
collections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
const handleImportSuccess = (collections: HoppCollection[]) => {
|
||||
appendRESTCollections(collections)
|
||||
toast.success(t("import.import_from_url_success").toString())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
Environment,
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { Environment, HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
@@ -14,15 +9,15 @@ import { PersistableTabState } from "~/services/tab"
|
||||
type VUEX_DATA = {
|
||||
postwoman: {
|
||||
settings?: SettingsDef
|
||||
collections?: HoppCollection<HoppRESTRequest>[]
|
||||
collectionsGraphql?: HoppCollection<HoppGQLRequest>[]
|
||||
collections?: HoppCollection[]
|
||||
collectionsGraphql?: HoppCollection[]
|
||||
environments?: Environment[]
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS = getDefaultSettings()
|
||||
|
||||
export const REST_COLLECTIONS_MOCK: HoppCollection<HoppRESTRequest>[] = [
|
||||
export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 1,
|
||||
name: "Echo",
|
||||
@@ -44,7 +39,7 @@ export const REST_COLLECTIONS_MOCK: HoppCollection<HoppRESTRequest>[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const GQL_COLLECTIONS_MOCK: HoppCollection<HoppGQLRequest>[] = [
|
||||
export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 1,
|
||||
name: "Echo",
|
||||
|
||||
@@ -3,7 +3,9 @@ import {
|
||||
GQLHeader,
|
||||
HoppGQLAuth,
|
||||
HoppGQLRequest,
|
||||
HoppRESTAuth,
|
||||
HoppRESTRequest,
|
||||
HoppRESTHeaders,
|
||||
} from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
@@ -62,12 +64,18 @@ const HoppGQLRequestSchema = entityReference(HoppGQLRequest)
|
||||
const HoppRESTCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppRESTCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppRESTRequestSchema)),
|
||||
|
||||
auth: z.optional(HoppRESTAuth),
|
||||
headers: z.optional(HoppRESTHeaders),
|
||||
}).strict()
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppGQLCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppGQLCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppGQLRequestSchema)),
|
||||
|
||||
auth: z.optional(HoppGQLAuth),
|
||||
headers: z.optional(z.array(GQLHeader)),
|
||||
}).strict()
|
||||
|
||||
export const VUEX_SCHEMA = z.object({
|
||||
@@ -276,6 +284,23 @@ const validGqlOperations = [
|
||||
"authorization",
|
||||
] as const
|
||||
|
||||
const HoppInheritedPropertySchema = z
|
||||
.object({
|
||||
auth: z.object({
|
||||
parentID: z.string(),
|
||||
parentName: z.string(),
|
||||
inheritedAuth: z.union([HoppRESTAuth, HoppGQLAuth]),
|
||||
}),
|
||||
headers: z.array(
|
||||
z.object({
|
||||
parentID: z.string(),
|
||||
parentName: z.string(),
|
||||
inheritedHeader: z.union([HoppRESTHeaders, GQLHeader]),
|
||||
})
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const GQL_TAB_STATE_SCHEMA = z
|
||||
.object({
|
||||
lastActiveTabID: z.string(),
|
||||
@@ -291,6 +316,7 @@ export const GQL_TAB_STATE_SCHEMA = z
|
||||
response: z.optional(z.nullable(GQLResponseEventSchema)),
|
||||
responseTabPreference: z.optional(z.string()),
|
||||
optionTabPreference: z.optional(z.enum(validGqlOperations)),
|
||||
inheritedProperties: z.optional(HoppInheritedPropertySchema),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
@@ -462,6 +488,7 @@ export const REST_TAB_STATE_SCHEMA = z
|
||||
testResults: z.optional(z.nullable(HoppTestResultSchema)),
|
||||
responseTabPreference: z.optional(z.string()),
|
||||
optionTabPreference: z.optional(z.enum(validRestOperations)),
|
||||
inheritedProperties: z.optional(HoppInheritedPropertySchema),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import MiniSearch from "minisearch"
|
||||
import {
|
||||
cascadeParentCollectionForHeaderAuth,
|
||||
graphqlCollectionStore,
|
||||
restCollectionStore,
|
||||
} from "~/newstore/collections"
|
||||
@@ -229,7 +230,7 @@ export class CollectionsSpotlightSearcherService
|
||||
|
||||
private getRESTFolderFromFolderPath(
|
||||
folderPath: string
|
||||
): HoppCollection<HoppRESTRequest> | undefined {
|
||||
): HoppCollection | undefined {
|
||||
try {
|
||||
const folderIndicies = folderPath.split("/").map((x) => parseInt(x))
|
||||
|
||||
@@ -253,7 +254,7 @@ export class CollectionsSpotlightSearcherService
|
||||
|
||||
private getGQLFolderFromFolderPath(
|
||||
folderPath: string
|
||||
): HoppCollection<HoppGQLRequest> | undefined {
|
||||
): HoppCollection | undefined {
|
||||
try {
|
||||
const folderIndicies = folderPath.split("/").map((x) => parseInt(x))
|
||||
|
||||
@@ -300,10 +301,15 @@ export class CollectionsSpotlightSearcherService
|
||||
this.restTab.setActiveTab(possibleTab.value.id)
|
||||
} else {
|
||||
const req = this.getRESTFolderFromFolderPath(folderPath.join("/"))
|
||||
?.requests[reqIndex]
|
||||
?.requests[reqIndex] as HoppRESTRequest
|
||||
|
||||
if (!req) return
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath.join("/"),
|
||||
"rest"
|
||||
)
|
||||
|
||||
this.restTab.createNewTab(
|
||||
{
|
||||
request: req,
|
||||
@@ -313,6 +319,10 @@ export class CollectionsSpotlightSearcherService
|
||||
folderPath: folderPath.join("/"),
|
||||
requestIndex: reqIndex,
|
||||
},
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
},
|
||||
true
|
||||
)
|
||||
@@ -322,10 +332,14 @@ export class CollectionsSpotlightSearcherService
|
||||
const reqIndex = folderPath.pop()!
|
||||
|
||||
const req = this.getGQLFolderFromFolderPath(folderPath.join("/"))
|
||||
?.requests[reqIndex]
|
||||
?.requests[reqIndex] as HoppGQLRequest
|
||||
|
||||
if (!req) return
|
||||
|
||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||
folderPath.join("/"),
|
||||
"graphql"
|
||||
)
|
||||
this.gqlTab.createNewTab({
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
@@ -334,6 +348,10 @@ export class CollectionsSpotlightSearcherService
|
||||
},
|
||||
request: req,
|
||||
isDirty: false,
|
||||
inheritedProperties: {
|
||||
auth,
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
import { GQL_REQ_SCHEMA_VERSION, HoppGQLRequest, translateToGQLRequest } from "../graphql";
|
||||
import { HoppRESTRequest, translateToNewRequest } from "../rest";
|
||||
import { InferredEntity, createVersionedEntity } from "verzod"
|
||||
|
||||
const CURRENT_COLL_SCHEMA_VER = 1
|
||||
import V1_VERSION from "./v/1"
|
||||
import V2_VERSION from "./v/2"
|
||||
|
||||
type SupportedReqTypes =
|
||||
| HoppRESTRequest
|
||||
| HoppGQLRequest
|
||||
import { z } from "zod"
|
||||
import { translateToNewRequest } from "../rest"
|
||||
import { translateToGQLRequest } from "../graphql"
|
||||
|
||||
export type HoppCollection<T extends SupportedReqTypes> = {
|
||||
v: number
|
||||
name: string
|
||||
folders: HoppCollection<T>[]
|
||||
requests: T[]
|
||||
const versionedObject = z.object({
|
||||
// v is a stringified number
|
||||
v: z.string().regex(/^\d+$/).transform(Number),
|
||||
})
|
||||
|
||||
id?: string // For Firestore ID data
|
||||
}
|
||||
export const HoppCollection = createVersionedEntity({
|
||||
latestVersion: 2,
|
||||
versionMap: {
|
||||
1: V1_VERSION,
|
||||
2: V2_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
const versionCheck = versionedObject.safeParse(data)
|
||||
|
||||
if (versionCheck.success) return versionCheck.data.v
|
||||
|
||||
// For V1 we have to check the schema
|
||||
const result = V1_VERSION.schema.safeParse(data)
|
||||
|
||||
return result.success ? 0 : null
|
||||
},
|
||||
})
|
||||
|
||||
export type HoppCollection = InferredEntity<typeof HoppCollection>
|
||||
|
||||
export const CollectionSchemaVersion = 2
|
||||
|
||||
/**
|
||||
* Generates a Collection object. This ignores the version number object
|
||||
* so it can be incremented independently without updating it everywhere
|
||||
* @param x The Collection Data
|
||||
* @returns The final collection
|
||||
*/
|
||||
export function makeCollection<T extends SupportedReqTypes>(
|
||||
x: Omit<HoppCollection<T>, "v">
|
||||
): HoppCollection<T> {
|
||||
export function makeCollection(x: Omit<HoppCollection, "v">): HoppCollection {
|
||||
return {
|
||||
v: CURRENT_COLL_SCHEMA_VER,
|
||||
...x
|
||||
v: CollectionSchemaVersion,
|
||||
...x,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,20 +51,23 @@ export function makeCollection<T extends SupportedReqTypes>(
|
||||
* @param x The collection object to load
|
||||
* @returns The proper new collection format
|
||||
*/
|
||||
export function translateToNewRESTCollection(
|
||||
x: any
|
||||
): HoppCollection<HoppRESTRequest> {
|
||||
if (x.v && x.v === 1) return x
|
||||
export function translateToNewRESTCollection(x: any): HoppCollection {
|
||||
if (x.v && x.v === CollectionSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewRESTCollection)
|
||||
const requests = (x.requests ?? []).map(translateToNewRequest)
|
||||
|
||||
const obj = makeCollection<HoppRESTRequest>({
|
||||
const auth = x.auth ?? { authType: "inherit", authActive: true }
|
||||
const headers = x.headers ?? []
|
||||
|
||||
const obj = makeCollection({
|
||||
name,
|
||||
folders,
|
||||
requests,
|
||||
auth,
|
||||
headers,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
@@ -62,24 +80,26 @@ export function translateToNewRESTCollection(
|
||||
* @param x The collection object to load
|
||||
* @returns The proper new collection format
|
||||
*/
|
||||
export function translateToNewGQLCollection(
|
||||
x: any
|
||||
): HoppCollection<HoppGQLRequest> {
|
||||
if (x.v && x.v === GQL_REQ_SCHEMA_VERSION) return x
|
||||
export function translateToNewGQLCollection(x: any): HoppCollection {
|
||||
if (x.v && x.v === CollectionSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewGQLCollection)
|
||||
const requests = (x.requests ?? []).map(translateToGQLRequest)
|
||||
|
||||
const obj = makeCollection<HoppGQLRequest>({
|
||||
const auth = x.auth ?? { authType: "inherit", authActive: true }
|
||||
const headers = x.headers ?? []
|
||||
|
||||
const obj = makeCollection({
|
||||
name,
|
||||
folders,
|
||||
requests,
|
||||
auth,
|
||||
headers,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
|
||||
36
packages/hoppscotch-data/src/collection/v/1.ts
Normal file
36
packages/hoppscotch-data/src/collection/v/1.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { defineVersion, entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { HoppRESTRequest } from "../../rest"
|
||||
import { HoppGQLRequest } from "../../graphql"
|
||||
|
||||
const baseCollectionSchema = z.object({
|
||||
v: z.literal(1),
|
||||
id: z.optional(z.string()), // For Firestore ID data
|
||||
|
||||
name: z.string(),
|
||||
requests: z.array(
|
||||
z.lazy(() =>
|
||||
z.union([
|
||||
entityReference(HoppRESTRequest),
|
||||
entityReference(HoppGQLRequest),
|
||||
])
|
||||
)
|
||||
),
|
||||
})
|
||||
|
||||
type Input = z.input<typeof baseCollectionSchema> & {
|
||||
folders: Input[]
|
||||
}
|
||||
|
||||
type Output = z.output<typeof baseCollectionSchema> & {
|
||||
folders: Output[]
|
||||
}
|
||||
|
||||
export const V1_SCHEMA: z.ZodType<Output, z.ZodTypeDef, Input> = baseCollectionSchema.extend({
|
||||
folders: z.lazy(() => z.array(V1_SCHEMA)),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: true,
|
||||
schema: V1_SCHEMA,
|
||||
})
|
||||
57
packages/hoppscotch-data/src/collection/v/2.ts
Normal file
57
packages/hoppscotch-data/src/collection/v/2.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { defineVersion, entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { HoppRESTRequest, HoppRESTAuth } from "../../rest"
|
||||
import { HoppGQLRequest, HoppGQLAuth, GQLHeader } from "../../graphql"
|
||||
import { V1_SCHEMA } from "./1"
|
||||
import { HoppRESTHeaders } from "../../rest/v/1"
|
||||
|
||||
const baseCollectionSchema = z.object({
|
||||
v: z.literal(2),
|
||||
id: z.optional(z.string()), // For Firestore ID data
|
||||
|
||||
name: z.string(),
|
||||
requests: z.array(
|
||||
z.lazy(() =>
|
||||
z.union([
|
||||
entityReference(HoppRESTRequest),
|
||||
entityReference(HoppGQLRequest),
|
||||
])
|
||||
)
|
||||
),
|
||||
|
||||
auth: z.union([HoppRESTAuth, HoppGQLAuth]),
|
||||
headers: z.union([HoppRESTHeaders, z.array(GQLHeader)]),
|
||||
})
|
||||
|
||||
type Input = z.input<typeof baseCollectionSchema> & {
|
||||
folders: Input[]
|
||||
}
|
||||
|
||||
type Output = z.output<typeof baseCollectionSchema> & {
|
||||
folders: Output[]
|
||||
}
|
||||
|
||||
export const V2_SCHEMA: z.ZodType<Output, z.ZodTypeDef, Input> = baseCollectionSchema.extend({
|
||||
folders: z.lazy(() => z.array(V2_SCHEMA)),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V2_SCHEMA,
|
||||
up(old: z.infer<typeof V1_SCHEMA>) {
|
||||
// @ts-expect-error
|
||||
const result: z.infer<typeof V2_SCHEMA> = {
|
||||
...old,
|
||||
v: 2,
|
||||
auth: {
|
||||
authActive: true,
|
||||
authType: "inherit",
|
||||
},
|
||||
headers: [],
|
||||
}
|
||||
|
||||
if (old.id) result.id = old.id
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
@@ -11,6 +11,7 @@ export {
|
||||
HoppGQLAuthBearer,
|
||||
HoppGQLAuthNone,
|
||||
HoppGQLAuthOAuth2,
|
||||
HoppGQLAuthInherit,
|
||||
} from "./v/2"
|
||||
|
||||
export const GQL_REQ_SCHEMA_VERSION = 2
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineVersion } from "verzod"
|
||||
import { GQLHeader, V1_SCHEMA } from "./1"
|
||||
|
||||
export const HoppGQLAuthNone = z.object({
|
||||
authType: z.literal("none")
|
||||
authType: z.literal("none"),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthNone = z.infer<typeof HoppGQLAuthNone>
|
||||
@@ -12,7 +12,7 @@ export const HoppGQLAuthBasic = z.object({
|
||||
authType: z.literal("basic"),
|
||||
|
||||
username: z.string().catch(""),
|
||||
password: z.string().catch("")
|
||||
password: z.string().catch(""),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthBasic = z.infer<typeof HoppGQLAuthBasic>
|
||||
@@ -20,7 +20,7 @@ export type HoppGQLAuthBasic = z.infer<typeof HoppGQLAuthBasic>
|
||||
export const HoppGQLAuthBearer = z.object({
|
||||
authType: z.literal("bearer"),
|
||||
|
||||
token: z.string().catch("")
|
||||
token: z.string().catch(""),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthBearer = z.infer<typeof HoppGQLAuthBearer>
|
||||
@@ -33,7 +33,7 @@ export const HoppGQLAuthOAuth2 = z.object({
|
||||
authURL: z.string().catch(""),
|
||||
accessTokenURL: z.string().catch(""),
|
||||
clientID: z.string().catch(""),
|
||||
scope: z.string().catch("")
|
||||
scope: z.string().catch(""),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthOAuth2 = z.infer<typeof HoppGQLAuthOAuth2>
|
||||
@@ -43,22 +43,31 @@ export const HoppGQLAuthAPIKey = z.object({
|
||||
|
||||
key: z.string().catch(""),
|
||||
value: z.string().catch(""),
|
||||
addTo: z.string().catch("Headers")
|
||||
addTo: z.string().catch("Headers"),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthAPIKey = z.infer<typeof HoppGQLAuthAPIKey>
|
||||
|
||||
export const HoppGQLAuth = z.discriminatedUnion("authType", [
|
||||
HoppGQLAuthNone,
|
||||
HoppGQLAuthBasic,
|
||||
HoppGQLAuthBearer,
|
||||
HoppGQLAuthOAuth2,
|
||||
HoppGQLAuthAPIKey
|
||||
]).and(
|
||||
z.object({
|
||||
authActive: z.boolean()
|
||||
})
|
||||
)
|
||||
export const HoppGQLAuthInherit = z.object({
|
||||
authType: z.literal("inherit"),
|
||||
})
|
||||
|
||||
export type HoppGQLAuthInherit = z.infer<typeof HoppGQLAuthInherit>
|
||||
|
||||
export const HoppGQLAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppGQLAuthNone,
|
||||
HoppGQLAuthBasic,
|
||||
HoppGQLAuthBearer,
|
||||
HoppGQLAuthOAuth2,
|
||||
HoppGQLAuthAPIKey,
|
||||
HoppGQLAuthInherit,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppGQLAuth = z.infer<typeof HoppGQLAuth>
|
||||
|
||||
@@ -72,7 +81,7 @@ const V2_SCHEMA = z.object({
|
||||
query: z.string(),
|
||||
variables: z.string(),
|
||||
|
||||
auth: HoppGQLAuth
|
||||
auth: HoppGQLAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
@@ -85,7 +94,7 @@ export default defineVersion({
|
||||
auth: {
|
||||
authActive: true,
|
||||
authType: "none",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -20,10 +20,12 @@ export {
|
||||
HoppRESTAuth,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTReqBody,
|
||||
HoppRESTHeaders,
|
||||
} from "./v/1"
|
||||
|
||||
const versionedObject = z.object({
|
||||
|
||||
@@ -3,27 +3,29 @@ import { z } from "zod"
|
||||
|
||||
import { V0_SCHEMA } from "./0"
|
||||
|
||||
export const FormDataKeyValue = z.object({
|
||||
key: z.string(),
|
||||
active: z.boolean()
|
||||
}).and(
|
||||
z.union([
|
||||
z.object({
|
||||
isFile: z.literal(true),
|
||||
value: z.array(z.instanceof(Blob).nullable())
|
||||
}),
|
||||
z.object({
|
||||
isFile: z.literal(false),
|
||||
value: z.string()
|
||||
})
|
||||
])
|
||||
)
|
||||
export const FormDataKeyValue = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
active: z.boolean(),
|
||||
})
|
||||
.and(
|
||||
z.union([
|
||||
z.object({
|
||||
isFile: z.literal(true),
|
||||
value: z.array(z.instanceof(Blob).nullable()),
|
||||
}),
|
||||
z.object({
|
||||
isFile: z.literal(false),
|
||||
value: z.string(),
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
export type FormDataKeyValue = z.infer<typeof FormDataKeyValue>
|
||||
|
||||
export const HoppRESTReqBodyFormData = z.object({
|
||||
contentType: z.literal("multipart/form-data"),
|
||||
body: z.array(FormDataKeyValue)
|
||||
body: z.array(FormDataKeyValue),
|
||||
})
|
||||
|
||||
export type HoppRESTReqBodyFormData = z.infer<typeof HoppRESTReqBodyFormData>
|
||||
@@ -31,11 +33,11 @@ export type HoppRESTReqBodyFormData = z.infer<typeof HoppRESTReqBodyFormData>
|
||||
export const HoppRESTReqBody = z.union([
|
||||
z.object({
|
||||
contentType: z.literal(null),
|
||||
body: z.literal(null).catch(null)
|
||||
body: z.literal(null).catch(null),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.literal("multipart/form-data"),
|
||||
body: z.array(FormDataKeyValue).catch([])
|
||||
body: z.array(FormDataKeyValue).catch([]),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.union([
|
||||
@@ -48,14 +50,14 @@ export const HoppRESTReqBody = z.union([
|
||||
z.literal("text/html"),
|
||||
z.literal("text/plain"),
|
||||
]),
|
||||
body: z.string().catch("")
|
||||
})
|
||||
body: z.string().catch(""),
|
||||
}),
|
||||
])
|
||||
|
||||
export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>
|
||||
|
||||
export const HoppRESTAuthNone = z.object({
|
||||
authType: z.literal("none")
|
||||
authType: z.literal("none"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthNone = z.infer<typeof HoppRESTAuthNone>
|
||||
@@ -96,17 +98,26 @@ export const HoppRESTAuthAPIKey = z.object({
|
||||
|
||||
export type HoppRESTAuthAPIKey = z.infer<typeof HoppRESTAuthAPIKey>
|
||||
|
||||
export const HoppRESTAuth = z.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey
|
||||
]).and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
export const HoppRESTAuthInherit = z.object({
|
||||
authType: z.literal("inherit"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthInherit = z.infer<typeof HoppRESTAuthInherit>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
|
||||
@@ -114,7 +125,7 @@ export const HoppRESTParams = z.array(
|
||||
z.object({
|
||||
key: z.string().catch(""),
|
||||
value: z.string().catch(""),
|
||||
active: z.boolean().catch(true)
|
||||
active: z.boolean().catch(true),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -124,7 +135,7 @@ export const HoppRESTHeaders = z.array(
|
||||
z.object({
|
||||
key: z.string().catch(""),
|
||||
value: z.string().catch(""),
|
||||
active: z.boolean().catch(true)
|
||||
active: z.boolean().catch(true),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -144,17 +155,21 @@ const V1_SCHEMA = z.object({
|
||||
|
||||
auth: HoppRESTAuth,
|
||||
|
||||
body: HoppRESTReqBody
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
function parseRequestBody(x: z.infer<typeof V0_SCHEMA>): z.infer<typeof V1_SCHEMA>["body"] {
|
||||
function parseRequestBody(
|
||||
x: z.infer<typeof V0_SCHEMA>
|
||||
): z.infer<typeof V1_SCHEMA>["body"] {
|
||||
return {
|
||||
contentType: "application/json",
|
||||
body: x.contentType === "application/json" ? x.rawParams ?? "" : "",
|
||||
}
|
||||
}
|
||||
|
||||
export function parseOldAuth(x: z.infer<typeof V0_SCHEMA>): z.infer<typeof V1_SCHEMA>["auth"] {
|
||||
export function parseOldAuth(
|
||||
x: z.infer<typeof V0_SCHEMA>
|
||||
): z.infer<typeof V1_SCHEMA>["auth"] {
|
||||
if (!x.auth || x.auth === "None")
|
||||
return {
|
||||
authType: "none",
|
||||
@@ -183,7 +198,16 @@ export default defineVersion({
|
||||
initial: false,
|
||||
schema: V1_SCHEMA,
|
||||
up(old: z.infer<typeof V0_SCHEMA>) {
|
||||
const { url, path, headers, params, name, method, preRequestScript, testScript } = old
|
||||
const {
|
||||
url,
|
||||
path,
|
||||
headers,
|
||||
params,
|
||||
name,
|
||||
method,
|
||||
preRequestScript,
|
||||
testScript,
|
||||
} = old
|
||||
|
||||
const endpoint = `${url}${path}`
|
||||
const body = parseRequestBody(old)
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": "./dist"
|
||||
},
|
||||
"include": ["src/*.ts"]
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/*.ts"]
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ type ExportedUserCollectionGQL = {
|
||||
function exportedCollectionToHoppCollection(
|
||||
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
|
||||
collectionType: "REST" | "GQL"
|
||||
): HoppCollection<HoppRESTRequest | HoppGQLRequest> {
|
||||
): HoppCollection {
|
||||
if (collectionType == "REST") {
|
||||
const restCollection = collection as ExportedUserCollectionREST
|
||||
|
||||
@@ -186,7 +186,7 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
exportedCollectionToHoppCollection(
|
||||
collection,
|
||||
"REST"
|
||||
) as HoppCollection<HoppRESTRequest>
|
||||
) as HoppCollection
|
||||
)
|
||||
)
|
||||
: setGraphqlCollections(
|
||||
@@ -195,7 +195,7 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
exportedCollectionToHoppCollection(
|
||||
collection,
|
||||
"GQL"
|
||||
) as HoppCollection<HoppGQLRequest>
|
||||
) as HoppCollection
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -718,7 +718,7 @@ export const def: CollectionsPlatformDef = {
|
||||
|
||||
function getCollectionPathFromCollectionID(
|
||||
collectionID: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
collections: HoppCollection[],
|
||||
parentPath?: string
|
||||
): string | null {
|
||||
for (const collectionIndex in collections) {
|
||||
@@ -742,7 +742,7 @@ function getCollectionPathFromCollectionID(
|
||||
|
||||
function getRequestPathFromRequestID(
|
||||
requestID: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
collections: HoppCollection[],
|
||||
parentPath?: string
|
||||
): { collectionPath: string; requestIndex: number } | null {
|
||||
for (const collectionIndex in collections) {
|
||||
@@ -774,7 +774,7 @@ function getRequestPathFromRequestID(
|
||||
function getRequestIndex(
|
||||
requestID: string,
|
||||
parentCollectionPath: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[]
|
||||
collections: HoppCollection[]
|
||||
) {
|
||||
const collection = navigateToFolderWithIndexPath(
|
||||
collections,
|
||||
|
||||
@@ -39,7 +39,7 @@ export const restRequestsMapper = createMapper<string, string>()
|
||||
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||
// TODO: use importCollectionsJSON to do this
|
||||
const recursivelySyncCollections = async (
|
||||
collection: HoppCollection<HoppRESTRequest>,
|
||||
collection: HoppCollection,
|
||||
collectionPath: string,
|
||||
parentUserCollectionID?: string
|
||||
) => {
|
||||
|
||||
@@ -36,7 +36,7 @@ export const gqlRequestsMapper = createMapper<string, string>()
|
||||
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||
// TODO: use importCollectionsJSON to do this
|
||||
const recursivelySyncCollections = async (
|
||||
collection: HoppCollection<HoppRESTRequest>,
|
||||
collection: HoppCollection,
|
||||
collectionPath: string,
|
||||
parentUserCollectionID?: string
|
||||
) => {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
mutation CreateGQLChildUserCollection(
|
||||
$title: String!
|
||||
$parentUserCollectionID: ID!
|
||||
$data: String
|
||||
) {
|
||||
createGQLChildUserCollection(
|
||||
title: $title
|
||||
parentUserCollectionID: $parentUserCollectionID
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mutation CreateGQLRootUserCollection($title: String!) {
|
||||
createGQLRootUserCollection(title: $title) {
|
||||
mutation CreateGQLRootUserCollection($title: String!, $data: String) {
|
||||
createGQLRootUserCollection(title: $title, data: $data) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
mutation CreateRESTChildUserCollection(
|
||||
$title: String!
|
||||
$parentUserCollectionID: ID!
|
||||
$data: String
|
||||
) {
|
||||
createRESTChildUserCollection(
|
||||
title: $title
|
||||
parentUserCollectionID: $parentUserCollectionID
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mutation CreateRESTRootUserCollection($title: String!) {
|
||||
createRESTRootUserCollection(title: $title) {
|
||||
mutation CreateRESTRootUserCollection($title: String!, $data: String) {
|
||||
createRESTRootUserCollection(title: $title, data: $data) {
|
||||
id
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
mutation UpdateUserCollection(
|
||||
$userCollectionID: ID!
|
||||
$newTitle: String
|
||||
$data: String
|
||||
) {
|
||||
updateUserCollection(
|
||||
userCollectionID: $userCollectionID
|
||||
newTitle: $newTitle
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ query GetGQLRootUserCollections {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
childrenGQL {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ query GetUserRootCollections {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
childrenREST {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ subscription UserCollectionCreated {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ subscription userCollectionUpdated {
|
||||
id
|
||||
title
|
||||
type
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -65,24 +65,29 @@ import {
|
||||
GetGqlRootUserCollectionsQueryVariables,
|
||||
GetGqlRootUserCollectionsDocument,
|
||||
ReqType,
|
||||
UpdateUserCollectionMutation,
|
||||
UpdateUserCollectionMutationVariables,
|
||||
UpdateUserCollectionDocument,
|
||||
} from "../../api/generated/graphql"
|
||||
|
||||
export const createRESTRootUserCollection = (title: string) =>
|
||||
export const createRESTRootUserCollection = (title: string, data?: string) =>
|
||||
runMutation<
|
||||
CreateRestRootUserCollectionMutation,
|
||||
CreateRestRootUserCollectionMutationVariables,
|
||||
""
|
||||
>(CreateRestRootUserCollectionDocument, {
|
||||
title,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createGQLRootUserCollection = (title: string) =>
|
||||
export const createGQLRootUserCollection = (title: string, data?: string) =>
|
||||
runMutation<
|
||||
CreateGqlRootUserCollectionMutation,
|
||||
CreateGqlRootUserCollectionMutationVariables,
|
||||
""
|
||||
>(CreateGqlRootUserCollectionDocument, {
|
||||
title,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createRESTUserRequest = (
|
||||
@@ -117,7 +122,8 @@ export const createGQLUserRequest = (
|
||||
|
||||
export const createRESTChildUserCollection = (
|
||||
title: string,
|
||||
parentUserCollectionID: string
|
||||
parentUserCollectionID: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
CreateRestChildUserCollectionMutation,
|
||||
@@ -126,11 +132,13 @@ export const createRESTChildUserCollection = (
|
||||
>(CreateRestChildUserCollectionDocument, {
|
||||
title,
|
||||
parentUserCollectionID,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const createGQLChildUserCollection = (
|
||||
title: string,
|
||||
parentUserCollectionID: string
|
||||
parentUserCollectionID: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
CreateGqlChildUserCollectionMutation,
|
||||
@@ -139,6 +147,7 @@ export const createGQLChildUserCollection = (
|
||||
>(CreateGqlChildUserCollectionDocument, {
|
||||
title,
|
||||
parentUserCollectionID,
|
||||
data,
|
||||
})()
|
||||
|
||||
export const deleteUserCollection = (userCollectionID: string) =>
|
||||
@@ -160,6 +169,17 @@ export const renameUserCollection = (
|
||||
""
|
||||
>(RenameUserCollectionDocument, { userCollectionID, newTitle })()
|
||||
|
||||
export const updateUserCollection = (
|
||||
userCollectionID: string,
|
||||
newTitle?: string,
|
||||
data?: string
|
||||
) =>
|
||||
runMutation<
|
||||
UpdateUserCollectionMutation,
|
||||
UpdateUserCollectionMutationVariables,
|
||||
""
|
||||
>(UpdateUserCollectionDocument, { userCollectionID, newTitle, data })()
|
||||
|
||||
export const moveUserCollection = (
|
||||
sourceCollectionID: string,
|
||||
destinationCollectionID?: string
|
||||
|
||||
@@ -89,6 +89,7 @@ type ExportedUserCollectionREST = {
|
||||
folders: ExportedUserCollectionREST[]
|
||||
requests: Array<HoppRESTRequest & { id: string }>
|
||||
name: string
|
||||
data: string
|
||||
}
|
||||
|
||||
type ExportedUserCollectionGQL = {
|
||||
@@ -96,18 +97,25 @@ type ExportedUserCollectionGQL = {
|
||||
folders: ExportedUserCollectionGQL[]
|
||||
requests: Array<HoppGQLRequest & { id: string }>
|
||||
name: string
|
||||
data: string
|
||||
}
|
||||
|
||||
function exportedCollectionToHoppCollection(
|
||||
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
|
||||
collectionType: "REST" | "GQL"
|
||||
): HoppCollection<HoppRESTRequest | HoppGQLRequest> {
|
||||
): HoppCollection {
|
||||
if (collectionType == "REST") {
|
||||
const restCollection = collection as ExportedUserCollectionREST
|
||||
|
||||
const data =
|
||||
restCollection.data && restCollection.data !== "null"
|
||||
? JSON.parse(restCollection.data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: false },
|
||||
headers: [],
|
||||
}
|
||||
return {
|
||||
id: restCollection.id,
|
||||
v: 1,
|
||||
v: 2,
|
||||
name: restCollection.name,
|
||||
folders: restCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
@@ -139,26 +147,40 @@ function exportedCollectionToHoppCollection(
|
||||
testScript,
|
||||
})
|
||||
),
|
||||
auth: data.auth,
|
||||
headers: data.headers,
|
||||
}
|
||||
} else {
|
||||
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||
const data =
|
||||
gqlCollection.data && gqlCollection.data !== "null"
|
||||
? JSON.parse(gqlCollection.data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: false },
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return {
|
||||
id: gqlCollection.id,
|
||||
v: 1,
|
||||
v: 2,
|
||||
name: gqlCollection.name,
|
||||
folders: gqlCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
),
|
||||
requests: gqlCollection.requests.map(
|
||||
({ v, auth, headers, name, id }) => ({
|
||||
({ v, auth, headers, name, id, query, url, variables }) => ({
|
||||
id,
|
||||
v,
|
||||
auth,
|
||||
headers,
|
||||
name,
|
||||
query,
|
||||
url,
|
||||
variables,
|
||||
})
|
||||
) as HoppGQLRequest[],
|
||||
auth: data.auth,
|
||||
headers: data.headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +190,6 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
undefined,
|
||||
collectionType == "REST" ? ReqType.Rest : ReqType.Gql
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const collectionsJSONString =
|
||||
res.right.exportUserCollectionsToJSON.exportedCollection
|
||||
@@ -177,7 +198,6 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
ExportedUserCollectionGQL | ExportedUserCollectionREST
|
||||
>
|
||||
).map((collection) => ({ v: 1, ...collection }))
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
collectionType == "REST"
|
||||
? setRESTCollections(
|
||||
@@ -186,7 +206,7 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
exportedCollectionToHoppCollection(
|
||||
collection,
|
||||
"REST"
|
||||
) as HoppCollection<HoppRESTRequest>
|
||||
) as HoppCollection
|
||||
)
|
||||
)
|
||||
: setGraphqlCollections(
|
||||
@@ -195,7 +215,7 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
|
||||
exportedCollectionToHoppCollection(
|
||||
collection,
|
||||
"GQL"
|
||||
) as HoppCollection<HoppGQLRequest>
|
||||
) as HoppCollection
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -292,19 +312,32 @@ function setupUserCollectionCreatedSubscription() {
|
||||
})
|
||||
} else {
|
||||
// root collections won't have parentCollectionID
|
||||
const data =
|
||||
res.right.userCollectionCreated.data &&
|
||||
res.right.userCollectionCreated.data != "null"
|
||||
? JSON.parse(res.right.userCollectionCreated.data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: false },
|
||||
headers: [],
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
collectionType == "GQL"
|
||||
? addGraphqlCollection({
|
||||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 1,
|
||||
v: 2,
|
||||
auth: data.auth,
|
||||
headers: data.headers,
|
||||
})
|
||||
: addRESTCollection({
|
||||
name: res.right.userCollectionCreated.title,
|
||||
folders: [],
|
||||
requests: [],
|
||||
v: 1,
|
||||
v: 2,
|
||||
auth: data.auth,
|
||||
headers: data?.headers,
|
||||
})
|
||||
|
||||
const localIndex = collectionStore.value.state.length - 1
|
||||
@@ -718,7 +751,7 @@ export const def: CollectionsPlatformDef = {
|
||||
|
||||
function getCollectionPathFromCollectionID(
|
||||
collectionID: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
collections: HoppCollection[],
|
||||
parentPath?: string
|
||||
): string | null {
|
||||
for (const collectionIndex in collections) {
|
||||
@@ -742,7 +775,7 @@ function getCollectionPathFromCollectionID(
|
||||
|
||||
function getRequestPathFromRequestID(
|
||||
requestID: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
collections: HoppCollection[],
|
||||
parentPath?: string
|
||||
): { collectionPath: string; requestIndex: number } | null {
|
||||
for (const collectionIndex in collections) {
|
||||
@@ -774,7 +807,7 @@ function getRequestPathFromRequestID(
|
||||
function getRequestIndex(
|
||||
requestID: string,
|
||||
parentCollectionPath: string,
|
||||
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[]
|
||||
collections: HoppCollection[]
|
||||
) {
|
||||
const collection = navigateToFolderWithIndexPath(
|
||||
collections,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
editUserRequest,
|
||||
moveUserCollection,
|
||||
moveUserRequest,
|
||||
renameUserCollection,
|
||||
updateUserCollection,
|
||||
updateUserCollectionOrder,
|
||||
} from "./collections.api"
|
||||
|
||||
@@ -39,7 +39,7 @@ export const restRequestsMapper = createMapper<string, string>()
|
||||
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||
// TODO: use importCollectionsJSON to do this
|
||||
const recursivelySyncCollections = async (
|
||||
collection: HoppCollection<HoppRESTRequest>,
|
||||
collection: HoppCollection,
|
||||
collectionPath: string,
|
||||
parentUserCollectionID?: string
|
||||
) => {
|
||||
@@ -47,27 +47,69 @@ const recursivelySyncCollections = async (
|
||||
|
||||
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||
if (!parentUserCollectionID) {
|
||||
const res = await createRESTRootUserCollection(collection.name)
|
||||
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
}
|
||||
const res = await createRESTRootUserCollection(
|
||||
collection.name,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
if (E.isRight(res)) {
|
||||
parentCollectionID = res.right.createRESTRootUserCollection.id
|
||||
|
||||
const returnedData = res.right.createRESTRootUserCollection.data
|
||||
? JSON.parse(res.right.createRESTRootUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
|
||||
} else {
|
||||
parentCollectionID = undefined
|
||||
}
|
||||
} else {
|
||||
// if parentUserCollectionID exists, create the collection as a child collection
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
}
|
||||
|
||||
const res = await createRESTChildUserCollection(
|
||||
collection.name,
|
||||
parentUserCollectionID
|
||||
parentUserCollectionID,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const childCollectionId = res.right.createRESTChildUserCollection.id
|
||||
|
||||
const returnedData = res.right.createRESTChildUserCollection.data
|
||||
? JSON.parse(res.right.createRESTChildUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
|
||||
removeDuplicateRESTCollectionOrFolder(
|
||||
childCollectionId,
|
||||
@@ -155,8 +197,13 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
[collectionIndex]
|
||||
)?.id
|
||||
|
||||
if (collectionID && collection.name) {
|
||||
renameUserCollection(collectionID, collection.name)
|
||||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
updateUserCollection(collectionID, collection.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async addFolder({ name, path }) {
|
||||
@@ -195,9 +242,12 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
)?.id
|
||||
|
||||
const folderName = folder.name
|
||||
|
||||
if (folderID && folderName) {
|
||||
renameUserCollection(folderID, folderName)
|
||||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
}
|
||||
if (folderID) {
|
||||
updateUserCollection(folderID, folderName, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async removeFolder({ folderID }) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
deleteUserCollection,
|
||||
deleteUserRequest,
|
||||
editGQLUserRequest,
|
||||
renameUserCollection,
|
||||
updateUserCollection,
|
||||
} from "./collections.api"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
@@ -36,7 +36,7 @@ export const gqlRequestsMapper = createMapper<string, string>()
|
||||
// temp implementation untill the backend implements an endpoint that accepts an entire collection
|
||||
// TODO: use importCollectionsJSON to do this
|
||||
const recursivelySyncCollections = async (
|
||||
collection: HoppCollection<HoppRESTRequest>,
|
||||
collection: HoppCollection,
|
||||
collectionPath: string,
|
||||
parentUserCollectionID?: string
|
||||
) => {
|
||||
@@ -44,12 +44,35 @@ const recursivelySyncCollections = async (
|
||||
|
||||
// if parentUserCollectionID does not exist, create the collection as a root collection
|
||||
if (!parentUserCollectionID) {
|
||||
const res = await createGQLRootUserCollection(collection.name)
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
}
|
||||
const res = await createGQLRootUserCollection(
|
||||
collection.name,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
parentCollectionID = res.right.createGQLRootUserCollection.id
|
||||
|
||||
const returnedData = res.right.createGQLRootUserCollection.data
|
||||
? JSON.parse(res.right.createGQLRootUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
}
|
||||
|
||||
collection.id = parentCollectionID
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
parentCollectionID,
|
||||
collectionPath
|
||||
@@ -59,15 +82,37 @@ const recursivelySyncCollections = async (
|
||||
}
|
||||
} else {
|
||||
// if parentUserCollectionID exists, create the collection as a child collection
|
||||
|
||||
const data = {
|
||||
auth: collection.auth ?? {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: collection.headers ?? [],
|
||||
}
|
||||
|
||||
const res = await createGQLChildUserCollection(
|
||||
collection.name,
|
||||
parentUserCollectionID
|
||||
parentUserCollectionID,
|
||||
JSON.stringify(data)
|
||||
)
|
||||
|
||||
if (E.isRight(res)) {
|
||||
const childCollectionId = res.right.createGQLChildUserCollection.id
|
||||
|
||||
const returnedData = res.right.createGQLChildUserCollection.data
|
||||
? JSON.parse(res.right.createGQLChildUserCollection.data)
|
||||
: {
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
headers: [],
|
||||
}
|
||||
|
||||
collection.id = childCollectionId
|
||||
collection.auth = returnedData.auth
|
||||
collection.headers = returnedData.headers
|
||||
|
||||
removeDuplicateGraphqlCollectionOrFolder(
|
||||
childCollectionId,
|
||||
@@ -158,8 +203,13 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
[collectionIndex]
|
||||
)?.id
|
||||
|
||||
if (collectionID && collection.name) {
|
||||
renameUserCollection(collectionID, collection.name)
|
||||
const data = {
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
}
|
||||
|
||||
if (collectionID) {
|
||||
updateUserCollection(collectionID, collection.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async addFolder({ name, path }) {
|
||||
@@ -197,8 +247,13 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
path.split("/").map((index) => parseInt(index))
|
||||
)?.id
|
||||
|
||||
if (folderBackendId && folder.name) {
|
||||
renameUserCollection(folderBackendId, folder.name)
|
||||
const data = {
|
||||
auth: folder.auth,
|
||||
headers: folder.headers,
|
||||
}
|
||||
|
||||
if (folderBackendId) {
|
||||
updateUserCollection(folderBackendId, folder.name, JSON.stringify(data))
|
||||
}
|
||||
},
|
||||
async removeFolder({ folderID }) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
--properties-primary-sticky-fold: 2.05rem;
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col overflow-y-auto max-h-[55vh]"
|
||||
:class="{ 'p-4': !fullWidth }"
|
||||
:class="{ 'p-4': !fullWidth && !fullWidthBody }"
|
||||
>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
@@ -117,6 +117,7 @@ withDefaults(
|
||||
dimissible: boolean
|
||||
placement: string
|
||||
fullWidth: boolean
|
||||
fullWidthBody: boolean
|
||||
styles: string
|
||||
closeText: string | null
|
||||
}>(),
|
||||
@@ -126,6 +127,7 @@ withDefaults(
|
||||
dimissible: true,
|
||||
placement: "top",
|
||||
fullWidth: false,
|
||||
fullWidthBody: false,
|
||||
styles: "sm:max-w-lg",
|
||||
closeText: null,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user