Skip to content

Commit 043f4ea

Browse files
chore: common services package (#6255)
* fix: initial services package setup * fix: services packages updates * fix: services changes * fix: merge conflicts * chore: file structuring * fix: import fixes
1 parent 1ee0661 commit 043f4ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2345
-10
lines changed

packages/constants/src/ai.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum AI_EDITOR_TASKS {
2+
ASK_ANYTHING = "ASK_ANYTHING",
3+
}

packages/constants/src/endpoints.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "";
2-
// PI Base Url
3-
export const PI_BASE_URL = process.env.NEXT_PUBLIC_PI_BASE_URL || "";
2+
export const API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH || "/";
3+
export const API_URL = encodeURI(`${API_BASE_URL}${API_BASE_PATH}`);
44
// God Mode Admin App Base Url
55
export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || "";
6-
export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "";
7-
export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}/`);
6+
export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "/";
7+
export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}`);
88
// Publish App Base Url
99
export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || "";
10-
export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
11-
export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}/`);
10+
export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "/";
11+
export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}`);
1212
// Live App Base Url
1313
export const LIVE_BASE_URL = process.env.NEXT_PUBLIC_LIVE_BASE_URL || "";
14-
export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || "";
15-
export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}/`);
14+
export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || "/";
15+
export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}`);
1616
// Web App Base Url
1717
export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
18-
export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || "";
18+
export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || "/";
1919
export const WEB_URL = encodeURI(`${WEB_BASE_URL}${WEB_BASE_PATH}`);
2020
// plane website url
2121
export const WEBSITE_URL =
2222
process.env.NEXT_PUBLIC_WEBSITE_URL || "https://plane.so";
23-
2423
// support email
2524
export const SUPPORT_EMAIL =
2625
process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "[email protected]";

packages/constants/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./ai";
12
export * from "./auth";
23
export * from "./endpoints";
34
export * from "./file";

packages/eslint-config/library.js

+24
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ module.exports = {
4444
"@typescript-eslint/no-explicit-any": "warn",
4545
"@typescript-eslint/no-useless-empty-export": "error",
4646
"@typescript-eslint/prefer-ts-expect-error": "warn",
47+
"import/order": [
48+
"warn",
49+
{
50+
groups: ["builtin", "external", "internal", "parent", "sibling"],
51+
pathGroups: [
52+
{
53+
pattern: "@plane/**",
54+
group: "external",
55+
position: "after",
56+
},
57+
{
58+
pattern: "@/**",
59+
group: "internal",
60+
position: "before",
61+
},
62+
],
63+
pathGroupsExcludedImportTypes: ["builtin", "internal", "react"],
64+
alphabetize: {
65+
order: "asc",
66+
caseInsensitive: true,
67+
},
68+
},
69+
],
4770
},
71+
4872
ignorePatterns: [".*.js", "node_modules/", "dist/"],
4973
};

packages/services/.eslintignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/*
2+
dist/*
3+
out/*

packages/services/.eslintrc.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type {import("eslint").Linter.Config} */
2+
module.exports = {
3+
root: true,
4+
extends: ["@plane/eslint-config/library.js"],
5+
parser: "@typescript-eslint/parser",
6+
parserOptions: {
7+
project: true,
8+
},
9+
};

packages/services/.prettierrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"printWidth": 120,
3+
"tabWidth": 2,
4+
"trailingComma": "es5"
5+
}

packages/services/package.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@plane/services",
3+
"version": "0.24.1",
4+
"private": true,
5+
"main": "./src/index.ts",
6+
"scripts": {
7+
"lint": "eslint src --ext .ts,.tsx",
8+
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
9+
},
10+
"dependencies": {
11+
"@plane/constants": "*",
12+
"axios": "^1.4.0"
13+
}
14+
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// plane web constants
2+
import { AI_EDITOR_TASKS, API_BASE_URL } from "@plane/constants";
3+
// services
4+
import { APIService } from "@/api.service";
5+
6+
/**
7+
* Payload type for AI editor tasks
8+
* @typedef {Object} TTaskPayload
9+
* @property {number} [casual_score] - Optional score for casual tone analysis
10+
* @property {number} [formal_score] - Optional score for formal tone analysis
11+
* @property {AI_EDITOR_TASKS} task - Type of AI editor task to perform
12+
* @property {string} text_input - The input text to be processed
13+
*/
14+
export type TTaskPayload = {
15+
casual_score?: number;
16+
formal_score?: number;
17+
task: AI_EDITOR_TASKS;
18+
text_input: string;
19+
};
20+
21+
/**
22+
* Service class for handling AI-related API operations
23+
* Extends the base APIService class to interact with AI endpoints
24+
* @extends {APIService}
25+
*/
26+
export class AIService extends APIService {
27+
constructor(BASE_URL?: string) {
28+
super(BASE_URL || API_BASE_URL);
29+
}
30+
31+
/**
32+
* Creates a GPT-based task for a specific workspace
33+
* @param {string} workspaceSlug - The unique identifier for the workspace
34+
* @param {Object} data - The data payload for the GPT task
35+
* @param {string} data.prompt - The prompt text for the GPT model
36+
* @param {string} data.task - The type of task to be performed
37+
* @returns {Promise<any>} The response data from the GPT task
38+
* @throws {Error} Throws the response error if the request fails
39+
*/
40+
async prompt(workspaceSlug: string, data: { prompt: string; task: string }): Promise<any> {
41+
return this.post(`/api/workspaces/${workspaceSlug}/ai-assistant/`, data)
42+
.then((response) => response?.data)
43+
.catch((error) => {
44+
throw error?.response;
45+
});
46+
}
47+
48+
/**
49+
* Performs an editor-specific AI task for text processing
50+
* @param {string} workspaceSlug - The unique identifier for the workspace
51+
* @param {TTaskPayload} data - The task payload containing text and processing parameters
52+
* @returns {Promise<{response: string}>} The processed text response
53+
* @throws {Error} Throws the response data if the request fails
54+
*/
55+
async rephraseGrammar(
56+
workspaceSlug: string,
57+
data: TTaskPayload
58+
): Promise<{
59+
response: string;
60+
}> {
61+
return this.post(`/api/workspaces/${workspaceSlug}/rephrase-grammar/`, data)
62+
.then((res) => res?.data)
63+
.catch((error) => {
64+
throw error?.response?.data;
65+
});
66+
}
67+
}

packages/services/src/ai/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./ai.service";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// constants
2+
import { API_BASE_URL } from "@plane/constants";
3+
// types
4+
import {
5+
IAnalyticsParams,
6+
IAnalyticsResponse,
7+
IDefaultAnalyticsResponse,
8+
IExportAnalyticsFormData,
9+
ISaveAnalyticsFormData,
10+
} from "@plane/types";
11+
// services
12+
import { APIService } from "../api.service";
13+
14+
export class AnalyticsService extends APIService {
15+
constructor(BASE_URL?: string) {
16+
super(BASE_URL || API_BASE_URL);
17+
}
18+
19+
/**
20+
* Retrieves analytics data for a specific workspace
21+
* @param {string} workspaceSlug - The unique identifier for the workspace
22+
* @param {IAnalyticsParams} params - Parameters for filtering analytics data
23+
* @param {string|number} [params.project] - Optional project identifier that will be converted to string
24+
* @returns {Promise<IAnalyticsResponse>} The analytics data for the workspace
25+
* @throws {Error} Throws response data if the request fails
26+
*/
27+
async getAnalytics(workspaceSlug: string, params: IAnalyticsParams): Promise<IAnalyticsResponse> {
28+
return this.get(`/api/workspaces/${workspaceSlug}/analytics/`, {
29+
params: {
30+
...params,
31+
project: params?.project ? params.project.toString() : null,
32+
},
33+
})
34+
.then((response) => response?.data)
35+
.catch((error) => {
36+
throw error?.response?.data;
37+
});
38+
}
39+
40+
/**
41+
* Retrieves default analytics data for a workspace
42+
* @param {string} workspaceSlug - The unique identifier for the workspace
43+
* @param {Partial<IAnalyticsParams>} [params] - Optional parameters for filtering default analytics
44+
* @param {string|number} [params.project] - Optional project identifier that will be converted to string
45+
* @returns {Promise<IDefaultAnalyticsResponse>} The default analytics data
46+
* @throws {Error} Throws response data if the request fails
47+
*/
48+
async getDefaultAnalytics(
49+
workspaceSlug: string,
50+
params?: Partial<IAnalyticsParams>
51+
): Promise<IDefaultAnalyticsResponse> {
52+
return this.get(`/api/workspaces/${workspaceSlug}/default-analytics/`, {
53+
params: {
54+
...params,
55+
project: params?.project ? params.project.toString() : null,
56+
},
57+
})
58+
.then((response) => response?.data)
59+
.catch((error) => {
60+
throw error?.response?.data;
61+
});
62+
}
63+
64+
/**
65+
* Saves analytics view configuration for a workspace
66+
* @param {string} workspaceSlug - The unique identifier for the workspace
67+
* @param {ISaveAnalyticsFormData} data - The analytics configuration data to save
68+
* @returns {Promise<any>} The response from saving the analytics view
69+
* @throws {Error} Throws response data if the request fails
70+
*/
71+
async save(workspaceSlug: string, data: ISaveAnalyticsFormData): Promise<any> {
72+
return this.post(`/api/workspaces/${workspaceSlug}/analytic-view/`, data)
73+
.then((response) => response?.data)
74+
.catch((error) => {
75+
throw error?.response?.data;
76+
});
77+
}
78+
79+
/**
80+
* Exports analytics data for a workspace
81+
* @param {string} workspaceSlug - The unique identifier for the workspace
82+
* @param {IExportAnalyticsFormData} data - Configuration for the analytics export
83+
* @returns {Promise<any>} The exported analytics data
84+
* @throws {Error} Throws response data if the request fails
85+
*/
86+
async export(workspaceSlug: string, data: IExportAnalyticsFormData): Promise<any> {
87+
return this.post(`/api/workspaces/${workspaceSlug}/export-analytics/`, data)
88+
.then((response) => response?.data)
89+
.catch((error) => {
90+
throw error?.response?.data;
91+
});
92+
}
93+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./analytics.service";

packages/services/src/api.service.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
3+
4+
/**
5+
* Abstract base class for making HTTP requests using axios
6+
* @abstract
7+
*/
8+
export abstract class APIService {
9+
protected baseURL: string;
10+
private axiosInstance: AxiosInstance;
11+
12+
/**
13+
* Creates an instance of APIService
14+
* @param {string} baseURL - The base URL for all HTTP requests
15+
*/
16+
constructor(baseURL: string) {
17+
this.baseURL = baseURL;
18+
this.axiosInstance = axios.create({
19+
baseURL,
20+
withCredentials: true,
21+
});
22+
23+
this.setupInterceptors();
24+
}
25+
26+
/**
27+
* Sets up axios interceptors for handling responses
28+
* Currently handles 401 unauthorized responses by redirecting to login
29+
* @private
30+
*/
31+
private setupInterceptors() {
32+
this.axiosInstance.interceptors.response.use(
33+
(response) => response,
34+
(error) => {
35+
if (error.response && error.response.status === 401) {
36+
const currentPath = window.location.pathname;
37+
window.location.replace(`/${currentPath ? `?next_path=${currentPath}` : ``}`);
38+
}
39+
return Promise.reject(error);
40+
}
41+
);
42+
}
43+
44+
/**
45+
* Makes a GET request to the specified URL
46+
* @param {string} url - The endpoint URL
47+
* @param {object} [params={}] - URL parameters
48+
* @param {AxiosRequestConfig} [config={}] - Additional axios configuration
49+
* @returns {Promise} Axios response promise
50+
*/
51+
get(url: string, params = {}, config: AxiosRequestConfig = {}) {
52+
return this.axiosInstance.get(url, {
53+
...params,
54+
...config,
55+
});
56+
}
57+
58+
/**
59+
* Makes a POST request to the specified URL
60+
* @param {string} url - The endpoint URL
61+
* @param {object} [data={}] - Request body data
62+
* @param {AxiosRequestConfig} [config={}] - Additional axios configuration
63+
* @returns {Promise} Axios response promise
64+
*/
65+
post(url: string, data = {}, config: AxiosRequestConfig = {}) {
66+
return this.axiosInstance.post(url, data, config);
67+
}
68+
69+
/**
70+
* Makes a PUT request to the specified URL
71+
* @param {string} url - The endpoint URL
72+
* @param {object} [data={}] - Request body data
73+
* @param {AxiosRequestConfig} [config={}] - Additional axios configuration
74+
* @returns {Promise} Axios response promise
75+
*/
76+
put(url: string, data = {}, config: AxiosRequestConfig = {}) {
77+
return this.axiosInstance.put(url, data, config);
78+
}
79+
80+
/**
81+
* Makes a PATCH request to the specified URL
82+
* @param {string} url - The endpoint URL
83+
* @param {object} [data={}] - Request body data
84+
* @param {AxiosRequestConfig} [config={}] - Additional axios configuration
85+
* @returns {Promise} Axios response promise
86+
*/
87+
patch(url: string, data = {}, config: AxiosRequestConfig = {}) {
88+
return this.axiosInstance.patch(url, data, config);
89+
}
90+
91+
/**
92+
* Makes a DELETE request to the specified URL
93+
* @param {string} url - The endpoint URL
94+
* @param {any} [data] - Request body data
95+
* @param {AxiosRequestConfig} [config={}] - Additional axios configuration
96+
* @returns {Promise} Axios response promise
97+
*/
98+
delete(url: string, data?: any, config: AxiosRequestConfig = {}) {
99+
return this.axiosInstance.delete(url, { data, ...config });
100+
}
101+
102+
/**
103+
* Makes a custom request with the provided configuration
104+
* @param {object} [config={}] - Axios request configuration
105+
* @returns {Promise} Axios response promise
106+
*/
107+
request(config = {}) {
108+
return this.axiosInstance(config);
109+
}
110+
}

0 commit comments

Comments
 (0)