Compare commits
11 Commits
hotfix/inf
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab495177da | ||
|
|
a3e6bae032 | ||
|
|
1b19b8aed5 | ||
|
|
66f20d10e1 | ||
|
|
32e9366609 | ||
|
|
e41e956273 | ||
|
|
a14870f3f0 | ||
|
|
0e96665254 | ||
|
|
efdc1c2f5d | ||
|
|
c5334d4c06 | ||
|
|
4f549974ed |
48
docker-compose.deploy.yml
Normal file
48
docker-compose.deploy.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Docker Compose config used for internal test and QA deployments
|
||||||
|
# This just spins up the AIO container along with an attached DB to the standard HTTP ports with subpath access mode
|
||||||
|
|
||||||
|
# TODO: Add Healthcheck for the AIO container
|
||||||
|
|
||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# The service that spins up all 3 services at once in one container
|
||||||
|
hoppscotch-aio:
|
||||||
|
container_name: hoppscotch-aio
|
||||||
|
restart: unless-stopped
|
||||||
|
build:
|
||||||
|
dockerfile: prod.Dockerfile
|
||||||
|
context: .
|
||||||
|
target: aio
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
|
||||||
|
- ENABLE_SUBPATH_BASED_ACCESS=true
|
||||||
|
depends_on:
|
||||||
|
hoppscotch-db:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "3080:80"
|
||||||
|
|
||||||
|
# The preset DB service, you can delete/comment the below lines if
|
||||||
|
# you are using an external postgres instance
|
||||||
|
# This will be exposed at port 5432
|
||||||
|
hoppscotch-db:
|
||||||
|
image: postgres:15
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
user: postgres
|
||||||
|
environment:
|
||||||
|
# The default user defined by the docker image
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
# NOTE: Please UPDATE THIS PASSWORD!
|
||||||
|
POSTGRES_PASSWORD: testpass
|
||||||
|
POSTGRES_DB: hoppscotch
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
|
||||||
|
]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
@@ -112,7 +112,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
dockerfile: packages/hoppscotch-backend/Dockerfile
|
dockerfile: packages/hoppscotch-backend/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
target: dev
|
target: prod
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
restart: always
|
restart: always
|
||||||
@@ -122,7 +122,7 @@ services:
|
|||||||
- PORT=3000
|
- PORT=3000
|
||||||
volumes:
|
volumes:
|
||||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||||
- ./packages/hoppscotch-backend/:/usr/src/app
|
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||||
- /usr/src/app/node_modules/
|
- /usr/src/app/node_modules/
|
||||||
depends_on:
|
depends_on:
|
||||||
hoppscotch-db:
|
hoppscotch-db:
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
-- AlterTable
|
-- This is a custom migration file which is not generated by Prisma.
|
||||||
ALTER TABLE
|
-- The aim of this migration is to add text search indices to the TeamCollection and TeamRequest tables.
|
||||||
|
|
||||||
|
-- Create Extension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
|
|
||||||
|
-- Create GIN Trigram Index for Team Collection title
|
||||||
|
CREATE INDEX
|
||||||
|
"TeamCollection_title_trgm_idx"
|
||||||
|
ON
|
||||||
"TeamCollection"
|
"TeamCollection"
|
||||||
ADD
|
USING
|
||||||
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED;
|
GIN (title gin_trgm_ops);
|
||||||
|
|
||||||
-- AlterTable
|
-- Create GIN Trigram Index for Team Collection title
|
||||||
ALTER TABLE
|
CREATE INDEX
|
||||||
|
"TeamRequest_title_trgm_idx"
|
||||||
|
ON
|
||||||
"TeamRequest"
|
"TeamRequest"
|
||||||
ADD
|
USING
|
||||||
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED;
|
GIN (title gin_trgm_ops);
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "TeamCollection_textSearch_idx" ON "TeamCollection" USING GIN (titleSearch);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "TeamRequest_textSearch_idx" ON "TeamRequest" USING GIN (titleSearch);
|
|
||||||
|
|||||||
@@ -321,25 +321,28 @@ export class InfraConfigService implements OnModuleInit {
|
|||||||
* Reset all the InfraConfigs to their default values (from .env)
|
* Reset all the InfraConfigs to their default values (from .env)
|
||||||
*/
|
*/
|
||||||
async reset() {
|
async reset() {
|
||||||
|
// These are all the infra-configs that should not be reset
|
||||||
|
const RESET_EXCLUSION_LIST = [
|
||||||
|
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||||
|
InfraConfigEnum.ANALYTICS_USER_ID,
|
||||||
|
InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
|
||||||
|
];
|
||||||
try {
|
try {
|
||||||
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
||||||
|
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
|
||||||
|
(p) => RESET_EXCLUSION_LIST.includes(p.name) === false,
|
||||||
|
);
|
||||||
|
|
||||||
await this.prisma.infraConfig.deleteMany({
|
await this.prisma.infraConfig.deleteMany({
|
||||||
where: { name: { in: infraConfigDefaultObjs.map((p) => p.name) } },
|
where: {
|
||||||
|
name: {
|
||||||
|
in: updatedInfraConfigDefaultObjs.map((p) => p.name),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hardcode t
|
|
||||||
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
|
|
||||||
(obj) => obj.name !== InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
|
||||||
);
|
|
||||||
await this.prisma.infraConfig.createMany({
|
await this.prisma.infraConfig.createMany({
|
||||||
data: [
|
data: updatedInfraConfigDefaultObjs,
|
||||||
...updatedInfraConfigDefaultObjs,
|
|
||||||
{
|
|
||||||
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
stopApp();
|
stopApp();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { throwHTTPErr } from 'src/utils';
|
|||||||
export class TeamCollectionController {
|
export class TeamCollectionController {
|
||||||
constructor(private readonly teamCollectionService: TeamCollectionService) {}
|
constructor(private readonly teamCollectionService: TeamCollectionService) {}
|
||||||
|
|
||||||
@Get('search/:teamID/:searchQuery')
|
@Get('search/:teamID')
|
||||||
@RequiresTeamRole(
|
@RequiresTeamRole(
|
||||||
TeamMemberRole.VIEWER,
|
TeamMemberRole.VIEWER,
|
||||||
TeamMemberRole.EDITOR,
|
TeamMemberRole.EDITOR,
|
||||||
@@ -21,7 +21,7 @@ export class TeamCollectionController {
|
|||||||
)
|
)
|
||||||
@UseGuards(JwtAuthGuard, RESTTeamMemberGuard)
|
@UseGuards(JwtAuthGuard, RESTTeamMemberGuard)
|
||||||
async searchByTitle(
|
async searchByTitle(
|
||||||
@Param('searchQuery') searchQuery: string,
|
@Query('searchQuery') searchQuery: string,
|
||||||
@Param('teamID') teamID: string,
|
@Param('teamID') teamID: string,
|
||||||
@Query('take') take: string,
|
@Query('take') take: string,
|
||||||
@Query('skip') skip: string,
|
@Query('skip') skip: string,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
TEAM_COLL_PARENT_TREE_GEN_FAILED,
|
TEAM_COLL_PARENT_TREE_GEN_FAILED,
|
||||||
} from '../errors';
|
} from '../errors';
|
||||||
import { PubSubService } from '../pubsub/pubsub.service';
|
import { PubSubService } from '../pubsub/pubsub.service';
|
||||||
import { isValidLength } from 'src/utils';
|
import { escapeSqlLikeString, isValidLength } from 'src/utils';
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
|
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||||
@@ -1125,7 +1125,7 @@ export class TeamCollectionService {
|
|||||||
id: searchResults[i].id,
|
id: searchResults[i].id,
|
||||||
path: !fetchedParentTree
|
path: !fetchedParentTree
|
||||||
? []
|
? []
|
||||||
: ([fetchedParentTree.right] as CollectionSearchNode[]),
|
: (fetchedParentTree.right as CollectionSearchNode[]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,14 +1148,20 @@ export class TeamCollectionService {
|
|||||||
skip: number,
|
skip: number,
|
||||||
) {
|
) {
|
||||||
const query = Prisma.sql`
|
const query = Prisma.sql`
|
||||||
select id,title,'collection' AS type
|
SELECT
|
||||||
from "TeamCollection"
|
id,title,'collection' AS type
|
||||||
where "TeamCollection"."teamID"=${teamID}
|
FROM
|
||||||
and titlesearch @@ to_tsquery(${searchQuery})
|
"TeamCollection"
|
||||||
order by ts_rank(titlesearch,to_tsquery(${searchQuery}))
|
WHERE
|
||||||
limit ${take}
|
"TeamCollection"."teamID"=${teamID}
|
||||||
|
AND
|
||||||
|
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
|
||||||
|
ORDER BY
|
||||||
|
similarity(title, ${searchQuery})
|
||||||
|
LIMIT ${take}
|
||||||
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query);
|
const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query);
|
||||||
return E.right(res);
|
return E.right(res);
|
||||||
@@ -1180,12 +1186,17 @@ export class TeamCollectionService {
|
|||||||
skip: number,
|
skip: number,
|
||||||
) {
|
) {
|
||||||
const query = Prisma.sql`
|
const query = Prisma.sql`
|
||||||
select id,title,request->>'method' as method,'request' AS type
|
SELECT
|
||||||
from "TeamRequest"
|
id,title,request->>'method' as method,'request' AS type
|
||||||
where "TeamRequest"."teamID"=${teamID}
|
FROM
|
||||||
and titlesearch @@ to_tsquery(${searchQuery})
|
"TeamRequest"
|
||||||
order by ts_rank(titlesearch,to_tsquery(${searchQuery}))
|
WHERE
|
||||||
limit ${take}
|
"TeamRequest"."teamID"=${teamID}
|
||||||
|
AND
|
||||||
|
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
|
||||||
|
ORDER BY
|
||||||
|
similarity(title, ${searchQuery})
|
||||||
|
LIMIT ${take}
|
||||||
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1250,45 +1261,53 @@ export class TeamCollectionService {
|
|||||||
* @returns The parent tree of the parent collections
|
* @returns The parent tree of the parent collections
|
||||||
*/
|
*/
|
||||||
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
|
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
|
||||||
function findChildren(id) {
|
function findChildren(id: string): CollectionSearchNode[] {
|
||||||
const collection = parentCollections.filter((item) => item.id === id)[0];
|
const collection = parentCollections.filter((item) => item.id === id)[0];
|
||||||
if (collection.parentID == null) {
|
if (collection.parentID == null) {
|
||||||
return {
|
return <CollectionSearchNode[]>[
|
||||||
id: collection.id,
|
{
|
||||||
title: collection.title,
|
id: collection.id,
|
||||||
type: 'collection',
|
title: collection.title,
|
||||||
path: [],
|
type: 'collection' as const,
|
||||||
};
|
path: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = {
|
const res = <CollectionSearchNode[]>[
|
||||||
id: collection.id,
|
{
|
||||||
title: collection.title,
|
id: collection.id,
|
||||||
type: 'collection',
|
title: collection.title,
|
||||||
path: findChildren(collection.parentID),
|
type: 'collection' as const,
|
||||||
};
|
path: findChildren(collection.parentID),
|
||||||
|
},
|
||||||
|
];
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentCollections.length > 0) {
|
if (parentCollections.length > 0) {
|
||||||
if (parentCollections[0].parentID == null) {
|
if (parentCollections[0].parentID == null) {
|
||||||
return {
|
return <CollectionSearchNode[]>[
|
||||||
|
{
|
||||||
|
id: parentCollections[0].id,
|
||||||
|
title: parentCollections[0].title,
|
||||||
|
type: 'collection',
|
||||||
|
path: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CollectionSearchNode[]>[
|
||||||
|
{
|
||||||
id: parentCollections[0].id,
|
id: parentCollections[0].id,
|
||||||
title: parentCollections[0].title,
|
title: parentCollections[0].title,
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
path: [],
|
path: findChildren(parentCollections[0].parentID),
|
||||||
};
|
},
|
||||||
}
|
];
|
||||||
|
|
||||||
return {
|
|
||||||
id: parentCollections[0].id,
|
|
||||||
title: parentCollections[0].title,
|
|
||||||
type: 'collection',
|
|
||||||
path: findChildren(parentCollections[0].parentID),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return <CollectionSearchNode[]>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -250,3 +250,39 @@ export function checkEnvironmentAuthProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds escape backslashes to the input so that it can be used inside
|
||||||
|
* SQL LIKE/ILIKE queries. Inspired by PHP's `mysql_real_escape_string`
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* Eg. "100%" -> "100\\%"
|
||||||
|
*
|
||||||
|
* Source: https://stackoverflow.com/a/32648526
|
||||||
|
*/
|
||||||
|
export function escapeSqlLikeString(str: string) {
|
||||||
|
if (typeof str != 'string') return str;
|
||||||
|
|
||||||
|
return str.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) {
|
||||||
|
switch (char) {
|
||||||
|
case '\0':
|
||||||
|
return '\\0';
|
||||||
|
case '\x08':
|
||||||
|
return '\\b';
|
||||||
|
case '\x09':
|
||||||
|
return '\\t';
|
||||||
|
case '\x1a':
|
||||||
|
return '\\z';
|
||||||
|
case '\n':
|
||||||
|
return '\\n';
|
||||||
|
case '\r':
|
||||||
|
return '\\r';
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
case '\\':
|
||||||
|
case '%':
|
||||||
|
return '\\' + char; // prepends a backslash to backslash, percent,
|
||||||
|
// and double/single quotes
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] =>
|
|||||||
* path of each request within collection-json file, failed-tests-report, errors,
|
* path of each request within collection-json file, failed-tests-report, errors,
|
||||||
* total execution duration for requests, pre-request-scripts, test-scripts.
|
* total execution duration for requests, pre-request-scripts, test-scripts.
|
||||||
* @returns True, if collection runner executed without any errors or failed test-cases.
|
* @returns True, if collection runner executed without any errors or failed test-cases.
|
||||||
* False, if errors occured or test-cases failed.
|
* False, if errors occurred or test-cases failed.
|
||||||
*/
|
*/
|
||||||
export const collectionsRunnerResult = (
|
export const collectionsRunnerResult = (
|
||||||
requestsReport: RequestReport[]
|
requestsReport: RequestReport[]
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const printTestsMetrics = (testsMetrics: TestMetrics) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints details of each reported error for a request with error code.
|
* Prints details of each reported error for a request with error code.
|
||||||
* @param path Request's path in collection for which errors occured.
|
* @param path Request's path in collection for which errors occurred.
|
||||||
* @param errorsReport List of errors reported.
|
* @param errorsReport List of errors reported.
|
||||||
*/
|
*/
|
||||||
export const printErrorsReport = (
|
export const printErrorsReport = (
|
||||||
|
|||||||
@@ -66,19 +66,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch, ref } from "vue"
|
import { watch, ref } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { HoppCollection } from "@hoppscotch/data"
|
import { HoppCollection, HoppRESTAuth, HoppRESTHeaders } from "@hoppscotch/data"
|
||||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
type EditingProperties = {
|
type EditingProperties = {
|
||||||
collection: HoppCollection | TeamCollection | null
|
collection: Partial<HoppCollection> | null
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
path: string
|
path: string
|
||||||
inheritedProperties: HoppInheritedProperty | undefined
|
inheritedProperties?: HoppInheritedProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -95,21 +94,23 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "set-collection-properties", newCollection: any): void
|
(
|
||||||
|
e: "set-collection-properties",
|
||||||
|
newCollection: Omit<EditingProperties, "inheritedProperties">
|
||||||
|
): void
|
||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const editableCollection = ref({
|
const editableCollection = ref<{
|
||||||
body: {
|
headers: HoppRESTHeaders
|
||||||
contentType: null,
|
auth: HoppRESTAuth
|
||||||
body: null,
|
}>({
|
||||||
},
|
|
||||||
headers: [],
|
headers: [],
|
||||||
auth: {
|
auth: {
|
||||||
authType: "inherit",
|
authType: "inherit",
|
||||||
authActive: false,
|
authActive: false,
|
||||||
},
|
},
|
||||||
}) as any
|
})
|
||||||
|
|
||||||
const selectedOptionTab = ref("headers")
|
const selectedOptionTab = ref("headers")
|
||||||
|
|
||||||
@@ -122,17 +123,13 @@ watch(
|
|||||||
(show) => {
|
(show) => {
|
||||||
if (show && props.editingProperties?.collection) {
|
if (show && props.editingProperties?.collection) {
|
||||||
editableCollection.value.auth = clone(
|
editableCollection.value.auth = clone(
|
||||||
props.editingProperties.collection.auth
|
props.editingProperties.collection.auth as HoppRESTAuth
|
||||||
)
|
)
|
||||||
editableCollection.value.headers = clone(
|
editableCollection.value.headers = clone(
|
||||||
props.editingProperties.collection.headers
|
props.editingProperties.collection.headers as HoppRESTHeaders
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
editableCollection.value = {
|
editableCollection.value = {
|
||||||
body: {
|
|
||||||
contentType: null,
|
|
||||||
body: null,
|
|
||||||
},
|
|
||||||
headers: [],
|
headers: [],
|
||||||
auth: {
|
auth: {
|
||||||
authType: "inherit",
|
authType: "inherit",
|
||||||
@@ -146,7 +143,6 @@ watch(
|
|||||||
const saveEditedCollection = () => {
|
const saveEditedCollection = () => {
|
||||||
if (!props.editingProperties) return
|
if (!props.editingProperties) return
|
||||||
const finalCollection = clone(editableCollection.value)
|
const finalCollection = clone(editableCollection.value)
|
||||||
delete finalCollection.body
|
|
||||||
const collection = {
|
const collection = {
|
||||||
path: props.editingProperties.path,
|
path: props.editingProperties.path,
|
||||||
collection: {
|
collection: {
|
||||||
@@ -155,7 +151,7 @@ const saveEditedCollection = () => {
|
|||||||
},
|
},
|
||||||
isRootCollection: props.editingProperties.isRootCollection,
|
isRootCollection: props.editingProperties.isRootCollection,
|
||||||
}
|
}
|
||||||
emit("set-collection-properties", collection)
|
emit("set-collection-properties", collection as EditingProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ const editingRequestIndex = ref<number | null>(null)
|
|||||||
const editingRequestID = ref<string | null>(null)
|
const editingRequestID = ref<string | null>(null)
|
||||||
|
|
||||||
const editingProperties = ref<{
|
const editingProperties = ref<{
|
||||||
collection: Omit<HoppCollection, "v"> | TeamCollection | null
|
collection: Partial<HoppCollection> | null
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
path: string
|
path: string
|
||||||
inheritedProperties?: HoppInheritedProperty
|
inheritedProperties?: HoppInheritedProperty
|
||||||
@@ -739,7 +739,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: createRequestInCollection.id,
|
requestID: createRequestInCollection.id,
|
||||||
collectionID: createRequestInCollection.collection.id,
|
collectionID: path,
|
||||||
teamID: createRequestInCollection.collection.team.id,
|
teamID: createRequestInCollection.collection.team.id,
|
||||||
},
|
},
|
||||||
inheritedProperties: {
|
inheritedProperties: {
|
||||||
@@ -2021,7 +2021,7 @@ const editProperties = (payload: {
|
|||||||
{
|
{
|
||||||
parentID: "",
|
parentID: "",
|
||||||
parentName: "",
|
parentName: "",
|
||||||
inheritedHeaders: [],
|
inheritedHeader: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as HoppInheritedProperty
|
} as HoppInheritedProperty
|
||||||
@@ -2039,7 +2039,7 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = {
|
editingProperties.value = {
|
||||||
collection,
|
collection: collection as Partial<HoppCollection>,
|
||||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||||
path: collectionIndex,
|
path: collectionIndex,
|
||||||
inheritedProperties,
|
inheritedProperties,
|
||||||
@@ -2083,7 +2083,7 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = {
|
editingProperties.value = {
|
||||||
collection: coll,
|
collection: coll as unknown as Partial<HoppCollection>,
|
||||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||||
path: collectionIndex,
|
path: collectionIndex,
|
||||||
inheritedProperties,
|
inheritedProperties,
|
||||||
@@ -2094,11 +2094,12 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setCollectionProperties = (newCollection: {
|
const setCollectionProperties = (newCollection: {
|
||||||
collection: HoppCollection
|
collection: Partial<HoppCollection> | null
|
||||||
path: string
|
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
|
path: string
|
||||||
}) => {
|
}) => {
|
||||||
const { collection, path, isRootCollection } = newCollection
|
const { collection, path, isRootCollection } = newCollection
|
||||||
|
if (!collection) return
|
||||||
|
|
||||||
if (collectionsType.value.type === "my-collections") {
|
if (collectionsType.value.type === "my-collections") {
|
||||||
if (isRootCollection) {
|
if (isRootCollection) {
|
||||||
@@ -2148,8 +2149,7 @@ const setCollectionProperties = (newCollection: {
|
|||||||
auth,
|
auth,
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
"rest",
|
"rest"
|
||||||
"team"
|
|
||||||
)
|
)
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { computed, reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
import { isEqual, cloneDeep } from "lodash-es"
|
import { isEqual, cloneDeep } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
|
HoppRESTAuth,
|
||||||
HoppRESTHeader,
|
HoppRESTHeader,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
parseRawKeyValueEntriesE,
|
parseRawKeyValueEntriesE,
|
||||||
@@ -364,7 +365,12 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
|||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: HoppRESTRequest
|
modelValue:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
}
|
||||||
isCollectionProperty?: boolean
|
isCollectionProperty?: boolean
|
||||||
inheritedProperties?: HoppInheritedProperty
|
inheritedProperties?: HoppInheritedProperty
|
||||||
envs?: AggregateEnvironment[]
|
envs?: AggregateEnvironment[]
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function onLoggedIn(exec: (user: HoppUser) => void) {
|
|||||||
* the auth system.
|
* the auth system.
|
||||||
*
|
*
|
||||||
* NOTE: Unlike `onLoggedIn` for which the callback will be called once on mount with the current state,
|
* NOTE: Unlike `onLoggedIn` for which the callback will be called once on mount with the current state,
|
||||||
* here the callback will only be called on authentication event occurances.
|
* here the callback will only be called on authentication event occurrences.
|
||||||
* You might want to check the auth state from an `onMounted` hook or something
|
* You might want to check the auth state from an `onMounted` hook or something
|
||||||
* if you want to access the initial state
|
* if you want to access the initial state
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to check the new folder path is close to the save context folder path or not
|
* 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 folderPathCurrent The path saved as the inherited path in the inherited properties
|
||||||
@@ -123,120 +122,109 @@ function folderPathCloseToSaveContext(
|
|||||||
saveContextPath: string
|
saveContextPath: string
|
||||||
) {
|
) {
|
||||||
if (!folderPathCurrent) return newFolderPath
|
if (!folderPathCurrent) return newFolderPath
|
||||||
|
|
||||||
const folderPathCurrentArray = folderPathCurrent.split("/")
|
const folderPathCurrentArray = folderPathCurrent.split("/")
|
||||||
const newFolderPathArray = newFolderPath.split("/")
|
const newFolderPathArray = newFolderPath.split("/")
|
||||||
|
|
||||||
const saveContextFolderPathArray = saveContextPath.split("/")
|
const saveContextFolderPathArray = saveContextPath.split("/")
|
||||||
|
|
||||||
let folderPathCurrentMatch = 0
|
const folderPathCurrentMatch = folderPathCurrentArray.filter(
|
||||||
|
(folder, i) => folder === saveContextFolderPathArray[i]
|
||||||
|
).length
|
||||||
|
|
||||||
for (let i = 0; i < folderPathCurrentArray.length; i++) {
|
const newFolderPathMatch = newFolderPathArray.filter(
|
||||||
if (folderPathCurrentArray[i] === saveContextFolderPathArray[i]) {
|
(folder, i) => folder === saveContextFolderPathArray[i]
|
||||||
folderPathCurrentMatch++
|
).length
|
||||||
|
|
||||||
|
return folderPathCurrentMatch > newFolderPathMatch
|
||||||
|
? folderPathCurrent
|
||||||
|
: newFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
||||||
|
const keyMap: { [key: string]: number[] } = {} // Map to store array of indices for each key
|
||||||
|
|
||||||
|
// Populate keyMap with the indices of each key
|
||||||
|
arr.forEach((item, index) => {
|
||||||
|
const key = item.inheritedHeader.key
|
||||||
|
if (!(key in keyMap)) {
|
||||||
|
keyMap[key] = []
|
||||||
|
}
|
||||||
|
keyMap[key].push(index)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a new array containing only the last occurrence of each key
|
||||||
|
const result = []
|
||||||
|
for (const key in keyMap) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keyMap, key)) {
|
||||||
|
const lastIndex = keyMap[key][keyMap[key].length - 1]
|
||||||
|
result.push(arr[lastIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let newFolderPathMatch = 0
|
// Sort the result array based on the parentID
|
||||||
|
result.sort((a, b) => a.parentID.localeCompare(b.parentID))
|
||||||
for (let i = 0; i < newFolderPathArray.length; i++) {
|
return result
|
||||||
if (newFolderPathArray[i] === saveContextFolderPathArray[i]) {
|
|
||||||
newFolderPathMatch++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderPathCurrentMatch > newFolderPathMatch) {
|
|
||||||
return folderPathCurrent
|
|
||||||
}
|
|
||||||
return newFolderPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateInheritedPropertiesForAffectedRequests(
|
export function updateInheritedPropertiesForAffectedRequests(
|
||||||
path: string,
|
path: string,
|
||||||
inheritedProperties: HoppInheritedProperty,
|
inheritedProperties: HoppInheritedProperty,
|
||||||
type: "rest" | "graphql",
|
type: "rest" | "graphql"
|
||||||
workspace: "personal" | "team" = "personal"
|
|
||||||
) {
|
) {
|
||||||
const tabService =
|
const tabService =
|
||||||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||||
|
|
||||||
let tabs
|
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
||||||
if (workspace === "personal") {
|
const saveContext = tab.document.saveContext
|
||||||
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) => {
|
const saveContextPath =
|
||||||
if (workspace === "personal") {
|
saveContext?.originLocation === "team-collection"
|
||||||
return (
|
? saveContext.collectionID
|
||||||
tab.value.document.saveContext?.originLocation === "user-collection" &&
|
: saveContext?.folderPath
|
||||||
tab.value.document.saveContext.folderPath.startsWith(path) &&
|
|
||||||
path ===
|
|
||||||
folderPathCloseToSaveContext(
|
|
||||||
tab.value.document.inheritedProperties?.auth.parentID,
|
|
||||||
path,
|
|
||||||
tab.value.document.saveContext.folderPath
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return saveContextPath?.startsWith(path) ?? false
|
||||||
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) => {
|
effectedTabs.map((tab) => {
|
||||||
return (
|
const inheritedParentID =
|
||||||
tab.value.document.inheritedProperties &&
|
tab.value.document.inheritedProperties?.auth.parentID
|
||||||
tab.value.document.inheritedProperties.headers.some(
|
|
||||||
|
const contextPath =
|
||||||
|
tab.value.document.saveContext?.originLocation === "team-collection"
|
||||||
|
? tab.value.document.saveContext.collectionID
|
||||||
|
: tab.value.document.saveContext?.folderPath
|
||||||
|
|
||||||
|
const effectedPath = folderPathCloseToSaveContext(
|
||||||
|
inheritedParentID,
|
||||||
|
path,
|
||||||
|
contextPath ?? ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (effectedPath === path) {
|
||||||
|
if (tab.value.document.inheritedProperties) {
|
||||||
|
tab.value.document.inheritedProperties.auth = inheritedProperties.auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.value.document.inheritedProperties?.headers) {
|
||||||
|
// filter out the headers with the parentID not as the path
|
||||||
|
const headers = tab.value.document.inheritedProperties.headers.filter(
|
||||||
|
(header) => header.parentID !== path
|
||||||
|
)
|
||||||
|
|
||||||
|
// filter out the headers with the parentID as the path in the inheritedProperties
|
||||||
|
const inheritedHeaders = inheritedProperties.headers.filter(
|
||||||
(header) => header.parentID === path
|
(header) => header.parentID === path
|
||||||
)
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const tab of tabsEffectedByAuth) {
|
// merge the headers with the parentID as the path
|
||||||
tab.value.document.inheritedProperties = inheritedProperties
|
const mergedHeaders = removeDuplicatesAndKeepLast([
|
||||||
}
|
...new Set([...inheritedHeaders, ...headers]),
|
||||||
|
])
|
||||||
|
|
||||||
for (const tab of tabsEffectedByHeaders) {
|
tab.value.document.inheritedProperties.headers = mergedHeaders
|
||||||
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) {
|
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
|
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
|
||||||
* (for eg. output -> { "key": "value" })
|
* (for eg. output -> { "key": "value" })
|
||||||
* NOTE: This function will discard duplicate key occurances and only keep the last occurance. If you do not want that behaviour,
|
* NOTE: This function will discard duplicate key occurrences and only keep the last occurrence. If you do not want that behaviour,
|
||||||
* use `tupleWithSamesKeysToRecord`.
|
* use `tupleWithSamesKeysToRecord`.
|
||||||
* @param tuples Array of tuples ([key, value])
|
* @param tuples Array of tuples ([key, value])
|
||||||
* @returns A record with value corresponding to the last occurance of that key
|
* @returns A record with value corresponding to the last occurrence of that key
|
||||||
*/
|
*/
|
||||||
export const tupleToRecord = <
|
export const tupleToRecord = <
|
||||||
KeyType extends string | number | symbol,
|
KeyType extends string | number | symbol,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
HoppRESTParam,
|
HoppRESTParam,
|
||||||
parseRawKeyValueEntriesE,
|
parseRawKeyValueEntriesE,
|
||||||
parseTemplateStringE,
|
parseTemplateStringE,
|
||||||
|
HoppRESTAuth,
|
||||||
|
HoppRESTHeaders,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { arrayFlatMap, arraySort } from "../functional/array"
|
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||||
import { toFormData } from "../functional/formData"
|
import { toFormData } from "../functional/formData"
|
||||||
@@ -44,7 +46,12 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
*/
|
*/
|
||||||
export const getComputedAuthHeaders = (
|
export const getComputedAuthHeaders = (
|
||||||
envVars: Environment["variables"],
|
envVars: Environment["variables"],
|
||||||
req?: HoppRESTRequest,
|
req?:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
},
|
||||||
auth?: HoppRESTRequest["auth"],
|
auth?: HoppRESTRequest["auth"],
|
||||||
parse = true
|
parse = true
|
||||||
) => {
|
) => {
|
||||||
@@ -108,7 +115,12 @@ export const getComputedAuthHeaders = (
|
|||||||
* @returns The list of headers
|
* @returns The list of headers
|
||||||
*/
|
*/
|
||||||
export const getComputedBodyHeaders = (
|
export const getComputedBodyHeaders = (
|
||||||
req: HoppRESTRequest
|
req:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
}
|
||||||
): HoppRESTHeader[] => {
|
): HoppRESTHeader[] => {
|
||||||
// If a content-type is already defined, that will override this
|
// If a content-type is already defined, that will override this
|
||||||
if (
|
if (
|
||||||
@@ -118,8 +130,10 @@ export const getComputedBodyHeaders = (
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if (!("body" in req)) return []
|
||||||
|
|
||||||
// Body should have a non-null content-type
|
// Body should have a non-null content-type
|
||||||
if (req.body.contentType === null) return []
|
if (!req.body || req.body.contentType === null) return []
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -143,7 +157,12 @@ export type ComputedHeader = {
|
|||||||
* @returns The headers that are generated along with the source of that header
|
* @returns The headers that are generated along with the source of that header
|
||||||
*/
|
*/
|
||||||
export const getComputedHeaders = (
|
export const getComputedHeaders = (
|
||||||
req: HoppRESTRequest,
|
req:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
},
|
||||||
envVars: Environment["variables"],
|
envVars: Environment["variables"],
|
||||||
parse = true
|
parse = true
|
||||||
): ComputedHeader[] => {
|
): ComputedHeader[] => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"@fontsource-variable/material-symbols-rounded": "5.0.5",
|
"@fontsource-variable/material-symbols-rounded": "5.0.5",
|
||||||
"@fontsource-variable/roboto-mono": "5.0.6",
|
"@fontsource-variable/roboto-mono": "5.0.6",
|
||||||
"@graphql-typed-document-node/core": "3.1.1",
|
"@graphql-typed-document-node/core": "3.1.1",
|
||||||
"@hoppscotch/ui": "0.1.2",
|
"@hoppscotch/ui": "0.1.3",
|
||||||
"@hoppscotch/vue-toasted": "0.1.0",
|
"@hoppscotch/vue-toasted": "0.1.0",
|
||||||
"@intlify/unplugin-vue-i18n": "1.2.0",
|
"@intlify/unplugin-vue-i18n": "1.2.0",
|
||||||
"@types/cors": "2.8.13",
|
"@types/cors": "2.8.13",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -1258,8 +1258,8 @@ importers:
|
|||||||
specifier: 3.1.1
|
specifier: 3.1.1
|
||||||
version: 3.1.1(graphql@16.6.0)
|
version: 3.1.1(graphql@16.6.0)
|
||||||
'@hoppscotch/ui':
|
'@hoppscotch/ui':
|
||||||
specifier: 0.1.2
|
specifier: 0.1.3
|
||||||
version: 0.1.2(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9)
|
version: 0.1.3(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9)
|
||||||
'@hoppscotch/vue-toasted':
|
'@hoppscotch/vue-toasted':
|
||||||
specifier: 0.1.0
|
specifier: 0.1.0
|
||||||
version: 0.1.0(vue@3.3.9)
|
version: 0.1.0(vue@3.3.9)
|
||||||
@@ -6050,8 +6050,8 @@ packages:
|
|||||||
- vite
|
- vite
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@hoppscotch/ui@0.1.2(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9):
|
/@hoppscotch/ui@0.1.3(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9):
|
||||||
resolution: {integrity: sha512-bBn7Km1iIFMBsXgnrDLqTBzz29XOPRZcRbQd18DZMYxoR7WQo9amBfa850vk5debYQx2+Mb0ExOnrGVO1QlBRg==}
|
resolution: {integrity: sha512-a1dmqqL+zS2P6cxkCBLdBtd+mD+MnCDSN63TrCPldW5W92rtqpeZ0bmGgiQlzfA2457JRktYpVCBR0Oc0J1jbA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: 3.3.9
|
vue: 3.3.9
|
||||||
@@ -24038,7 +24038,6 @@ packages:
|
|||||||
|
|
||||||
/workbox-google-analytics@6.6.0:
|
/workbox-google-analytics@6.6.0:
|
||||||
resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==}
|
resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==}
|
||||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
|
||||||
dependencies:
|
dependencies:
|
||||||
workbox-background-sync: 6.6.0
|
workbox-background-sync: 6.6.0
|
||||||
workbox-core: 6.6.0
|
workbox-core: 6.6.0
|
||||||
@@ -24048,7 +24047,6 @@ packages:
|
|||||||
|
|
||||||
/workbox-google-analytics@7.0.0:
|
/workbox-google-analytics@7.0.0:
|
||||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
||||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
|
||||||
dependencies:
|
dependencies:
|
||||||
workbox-background-sync: 7.0.0
|
workbox-background-sync: 7.0.0
|
||||||
workbox-core: 7.0.0
|
workbox-core: 7.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user