Add Workfeed module with configuration management, diagnostics, and API integration:
- Implement `Workfeed` module with endpoints for employees, shifts, departments, and diagnostics. - Add configuration UI for enabling Workfeed, API settings (`api_url`, `api_key`), and integration diagnostics. - Add unit tests for module schema validation, API endpoints, and diagnostics contract. - Introduce smoke test for Workfeed configuration and modal diagnostics. - Extend i18n locales to support Workfeed integration.
This commit is contained in:
@@ -155,6 +155,13 @@ const menu_items = ref([
|
||||
children: [],
|
||||
hidden: false
|
||||
},
|
||||
{
|
||||
label: SessionUser.superUser.modules.workfeed.meta.title,
|
||||
value: SessionUser.superUser.modules.workfeed.meta.config_endpoint,
|
||||
icon: SessionUser.superUser.modules.workfeed.meta.icon,
|
||||
children: [],
|
||||
hidden: false
|
||||
},
|
||||
],
|
||||
onSelect: async (value) => {
|
||||
SessionUser.functions.redirectTo.superUser(value);
|
||||
|
||||
@@ -44,6 +44,7 @@ const items = computed<NavigationItemProps[]>(() => [
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.stripe.meta.labels.multiple), to: '/superuser/configuration/stripe'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.fxratesapi.meta.labels.multiple), to: '/superuser/configuration/fxratesapi'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.weatherapi.meta.labels.multiple), to: '/superuser/configuration/weatherapi'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.workfeed.meta.labels.multiple), to: '/superuser/configuration/workfeed'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.gatewayapi.meta.labels.multiple), to: '/superuser/configuration/gatewayapi'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.entra.meta.labels.multiple), to: '/superuser/configuration/entra'},
|
||||
{ label: firstToUpperCase(SessionUser.superUser.modules.limble.meta.labels.multiple), to: '/superuser/configuration/limble'},
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<script>
|
||||
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
||||
|
||||
/**
|
||||
* The Workfeed -> Config object
|
||||
*/
|
||||
export const Config = {
|
||||
get: async (variable) => {
|
||||
return authenticatedRequest(
|
||||
"/workfeed/config?variable=" + variable,
|
||||
"GET"
|
||||
);
|
||||
},
|
||||
get_all: async () => {
|
||||
return authenticatedRequest(
|
||||
"/workfeed/config",
|
||||
"GET"
|
||||
);
|
||||
},
|
||||
set: async (variable, value) => {
|
||||
return authenticatedRequest(
|
||||
"/workfeed/config",
|
||||
"POST",
|
||||
{
|
||||
variable: variable,
|
||||
value: value
|
||||
}
|
||||
);
|
||||
},
|
||||
keys: {
|
||||
api_url: {
|
||||
get: async () => {
|
||||
return Config.get("api_url");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("api_url", value);
|
||||
},
|
||||
},
|
||||
api_key: {
|
||||
get: async () => {
|
||||
return Config.get("api_key");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("api_key", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
get: async () => {
|
||||
return Config.get("enabled");
|
||||
},
|
||||
set: async (enabled) => {
|
||||
return Config.set("enabled", enabled);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script>
|
||||
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
||||
import { Config } from "@/components/session/token/superUser/modules/workfeed/Config.vue";
|
||||
|
||||
const sanitizeQuery = (query = {}) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(query).filter(([, value]) => value !== undefined && value !== null && value !== "")
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The Workfeed object
|
||||
*/
|
||||
export const Workfeed = {
|
||||
meta: {
|
||||
title: "Workfeed",
|
||||
icon: "fas fa-users-cog",
|
||||
description: "The Workfeed integration",
|
||||
endpoint: "/modules/workfeed",
|
||||
config_endpoint: "/configuration/workfeed",
|
||||
labels: {
|
||||
single: "workfeed",
|
||||
multiple: "workfeed",
|
||||
}
|
||||
},
|
||||
config: Config,
|
||||
functions: {
|
||||
listEmployees: async ({
|
||||
search,
|
||||
departmentId,
|
||||
includeInactive,
|
||||
limit,
|
||||
cursor,
|
||||
} = {}) => {
|
||||
return authenticatedRequest(
|
||||
"/modules/workfeed/employees",
|
||||
"GET",
|
||||
sanitizeQuery({
|
||||
search,
|
||||
departmentId,
|
||||
includeInactive,
|
||||
limit,
|
||||
cursor,
|
||||
})
|
||||
);
|
||||
},
|
||||
getEmployee: async (id) => {
|
||||
return authenticatedRequest(
|
||||
"/modules/workfeed/employees/" + encodeURIComponent(id),
|
||||
"GET"
|
||||
);
|
||||
},
|
||||
listShifts: async ({
|
||||
from,
|
||||
to,
|
||||
departmentId,
|
||||
employeeId,
|
||||
status,
|
||||
limit,
|
||||
cursor,
|
||||
} = {}) => {
|
||||
return authenticatedRequest(
|
||||
"/modules/workfeed/shifts",
|
||||
"GET",
|
||||
sanitizeQuery({
|
||||
from,
|
||||
to,
|
||||
departmentId,
|
||||
employeeId,
|
||||
status,
|
||||
limit,
|
||||
cursor,
|
||||
})
|
||||
);
|
||||
},
|
||||
getShift: async (id) => {
|
||||
return authenticatedRequest(
|
||||
"/modules/workfeed/shifts/" + encodeURIComponent(id),
|
||||
"GET"
|
||||
);
|
||||
},
|
||||
listDepartments: async ({
|
||||
search,
|
||||
limit,
|
||||
cursor,
|
||||
} = {}) => {
|
||||
return authenticatedRequest(
|
||||
"/modules/workfeed/departments",
|
||||
"GET",
|
||||
sanitizeQuery({
|
||||
search,
|
||||
limit,
|
||||
cursor,
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -11,6 +11,7 @@ import { MotorAPI } from "@/components/session/token/superUser/modules/motorapi/
|
||||
import { Stripe } from "@/components/session/token/superUser/modules/stripe/Stripe.vue";
|
||||
import { FXRatesAPI } from "@/components/session/token/superUser/modules/fxratesapi/FXRatesAPI.vue";
|
||||
import { WeatherAPI } from "@/components/session/token/superUser/modules/weatherapi/WeatherAPI.vue";
|
||||
import { Workfeed } from "@/components/session/token/superUser/modules/workfeed/Workfeed.vue";
|
||||
import { GatewayAPI } from "@/components/session/token/superUser/modules/gatewayapi/GatewayAPI.vue";
|
||||
import { XLVask } from "@/components/session/token/superUser/modules/xlvask/XLVask.vue";
|
||||
import { Entra } from "@/components/session/token/superUser/modules/entra/Entra.vue";
|
||||
@@ -41,6 +42,7 @@ export const SuperUserObject = {
|
||||
get stripe() { return Stripe; },
|
||||
get fxratesapi() { return FXRatesAPI; },
|
||||
get weatherapi() { return WeatherAPI; },
|
||||
get workfeed() { return Workfeed; },
|
||||
get gatewayapi() { return GatewayAPI; },
|
||||
get xlvask() { return XLVask; },
|
||||
get entra() { return Entra; },
|
||||
|
||||
@@ -1149,6 +1149,46 @@
|
||||
"title": "XLVask konfiguration",
|
||||
"username": "Brugernavn",
|
||||
"username_desc": "Brugernavnet bruges til at autentificere API forbindelsen."
|
||||
},
|
||||
"workfeed": {
|
||||
"title": "Workfeed configuration",
|
||||
"subtitle": "Configuration of the Workfeed integration",
|
||||
"general_settings": "General settings",
|
||||
"general_settings_desc": "General settings for the Workfeed integration.",
|
||||
"enable": "Enable Workfeed",
|
||||
"enable_desc": "Enable or disable the Workfeed integration.",
|
||||
"api_settings": "API connection settings",
|
||||
"api_settings_desc": "The API connection settings used for Workfeed requests.",
|
||||
"api_url": "API URL",
|
||||
"api_url_desc": "The base URL for the Workfeed API.",
|
||||
"api_key": "API key",
|
||||
"api_key_desc": "The API key used to authenticate requests to Workfeed.",
|
||||
"close": "Close",
|
||||
"lookup": "Lookup",
|
||||
"validation_required": "This field is required.",
|
||||
"request_failed": "Request failed: {message}",
|
||||
"diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"description": "Run test lookups against the Workfeed integration endpoints.",
|
||||
"list_departments": "List departments",
|
||||
"list_employees": "List employees",
|
||||
"list_shifts": "List shifts",
|
||||
"get_employee": "Get employee by ID",
|
||||
"get_shift": "Get shift by ID",
|
||||
"prompt_search_optional": "Optional search text",
|
||||
"prompt_status_optional": "Optional shift status",
|
||||
"prompt_employee_id": "Employee ID",
|
||||
"prompt_shift_id": "Shift ID",
|
||||
"placeholder_search": "e.g. north facility",
|
||||
"placeholder_status": "e.g. published",
|
||||
"placeholder_employee_id": "e.g. emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
"placeholder_shift_id": "e.g. shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
"result_departments": "Department lookup result",
|
||||
"result_employees": "Employee lookup result",
|
||||
"result_shifts": "Shift lookup result",
|
||||
"result_employee": "Employee result",
|
||||
"result_shift": "Shift result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer_creation": {
|
||||
@@ -4279,4 +4319,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1149,6 +1149,46 @@
|
||||
"title": "XLVask-Konfiguration",
|
||||
"username": "Benutzername",
|
||||
"username_desc": "Der Benutzername wird zur Authentifizierung der API-Verbindung verwendet."
|
||||
},
|
||||
"workfeed": {
|
||||
"title": "Workfeed configuration",
|
||||
"subtitle": "Configuration of the Workfeed integration",
|
||||
"general_settings": "General settings",
|
||||
"general_settings_desc": "General settings for the Workfeed integration.",
|
||||
"enable": "Enable Workfeed",
|
||||
"enable_desc": "Enable or disable the Workfeed integration.",
|
||||
"api_settings": "API connection settings",
|
||||
"api_settings_desc": "The API connection settings used for Workfeed requests.",
|
||||
"api_url": "API URL",
|
||||
"api_url_desc": "The base URL for the Workfeed API.",
|
||||
"api_key": "API key",
|
||||
"api_key_desc": "The API key used to authenticate requests to Workfeed.",
|
||||
"close": "Close",
|
||||
"lookup": "Lookup",
|
||||
"validation_required": "This field is required.",
|
||||
"request_failed": "Request failed: {message}",
|
||||
"diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"description": "Run test lookups against the Workfeed integration endpoints.",
|
||||
"list_departments": "List departments",
|
||||
"list_employees": "List employees",
|
||||
"list_shifts": "List shifts",
|
||||
"get_employee": "Get employee by ID",
|
||||
"get_shift": "Get shift by ID",
|
||||
"prompt_search_optional": "Optional search text",
|
||||
"prompt_status_optional": "Optional shift status",
|
||||
"prompt_employee_id": "Employee ID",
|
||||
"prompt_shift_id": "Shift ID",
|
||||
"placeholder_search": "e.g. north facility",
|
||||
"placeholder_status": "e.g. published",
|
||||
"placeholder_employee_id": "e.g. emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
"placeholder_shift_id": "e.g. shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
"result_departments": "Department lookup result",
|
||||
"result_employees": "Employee lookup result",
|
||||
"result_shifts": "Shift lookup result",
|
||||
"result_employee": "Employee result",
|
||||
"result_shift": "Shift result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer_creation": {
|
||||
@@ -4277,4 +4317,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1149,6 +1149,46 @@
|
||||
"title": "XLVask configuration",
|
||||
"username": "Username",
|
||||
"username_desc": "The username is used to authenticate the API connection."
|
||||
},
|
||||
"workfeed": {
|
||||
"title": "Workfeed configuration",
|
||||
"subtitle": "Configuration of the Workfeed integration",
|
||||
"general_settings": "General settings",
|
||||
"general_settings_desc": "General settings for the Workfeed integration.",
|
||||
"enable": "Enable Workfeed",
|
||||
"enable_desc": "Enable or disable the Workfeed integration.",
|
||||
"api_settings": "API connection settings",
|
||||
"api_settings_desc": "The API connection settings used for Workfeed requests.",
|
||||
"api_url": "API URL",
|
||||
"api_url_desc": "The base URL for the Workfeed API.",
|
||||
"api_key": "API key",
|
||||
"api_key_desc": "The API key used to authenticate requests to Workfeed.",
|
||||
"close": "Close",
|
||||
"lookup": "Lookup",
|
||||
"validation_required": "This field is required.",
|
||||
"request_failed": "Request failed: {message}",
|
||||
"diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"description": "Run test lookups against the Workfeed integration endpoints.",
|
||||
"list_departments": "List departments",
|
||||
"list_employees": "List employees",
|
||||
"list_shifts": "List shifts",
|
||||
"get_employee": "Get employee by ID",
|
||||
"get_shift": "Get shift by ID",
|
||||
"prompt_search_optional": "Optional search text",
|
||||
"prompt_status_optional": "Optional shift status",
|
||||
"prompt_employee_id": "Employee ID",
|
||||
"prompt_shift_id": "Shift ID",
|
||||
"placeholder_search": "e.g. north facility",
|
||||
"placeholder_status": "e.g. published",
|
||||
"placeholder_employee_id": "e.g. emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
"placeholder_shift_id": "e.g. shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
"result_departments": "Department lookup result",
|
||||
"result_employees": "Employee lookup result",
|
||||
"result_shifts": "Shift lookup result",
|
||||
"result_employee": "Employee result",
|
||||
"result_shift": "Shift result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer_creation": {
|
||||
|
||||
@@ -1144,6 +1144,46 @@
|
||||
"title": "XLVask-konfigurasjon",
|
||||
"username": "Brukernavn",
|
||||
"username_desc": "Brukernavnet brukes til å autentisere API-tilkoblingen."
|
||||
},
|
||||
"workfeed": {
|
||||
"title": "Workfeed configuration",
|
||||
"subtitle": "Configuration of the Workfeed integration",
|
||||
"general_settings": "General settings",
|
||||
"general_settings_desc": "General settings for the Workfeed integration.",
|
||||
"enable": "Enable Workfeed",
|
||||
"enable_desc": "Enable or disable the Workfeed integration.",
|
||||
"api_settings": "API connection settings",
|
||||
"api_settings_desc": "The API connection settings used for Workfeed requests.",
|
||||
"api_url": "API URL",
|
||||
"api_url_desc": "The base URL for the Workfeed API.",
|
||||
"api_key": "API key",
|
||||
"api_key_desc": "The API key used to authenticate requests to Workfeed.",
|
||||
"close": "Close",
|
||||
"lookup": "Lookup",
|
||||
"validation_required": "This field is required.",
|
||||
"request_failed": "Request failed: {message}",
|
||||
"diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"description": "Run test lookups against the Workfeed integration endpoints.",
|
||||
"list_departments": "List departments",
|
||||
"list_employees": "List employees",
|
||||
"list_shifts": "List shifts",
|
||||
"get_employee": "Get employee by ID",
|
||||
"get_shift": "Get shift by ID",
|
||||
"prompt_search_optional": "Optional search text",
|
||||
"prompt_status_optional": "Optional shift status",
|
||||
"prompt_employee_id": "Employee ID",
|
||||
"prompt_shift_id": "Shift ID",
|
||||
"placeholder_search": "e.g. north facility",
|
||||
"placeholder_status": "e.g. published",
|
||||
"placeholder_employee_id": "e.g. emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
"placeholder_shift_id": "e.g. shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
"result_departments": "Department lookup result",
|
||||
"result_employees": "Employee lookup result",
|
||||
"result_shifts": "Shift lookup result",
|
||||
"result_employee": "Employee result",
|
||||
"result_shift": "Shift result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer_creation": {
|
||||
@@ -4227,4 +4267,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1144,6 +1144,46 @@
|
||||
"title": "XLVask konfiguration",
|
||||
"username": "Användarnamn",
|
||||
"username_desc": "Användarnamnet används för att autentisera API-anslutningen."
|
||||
},
|
||||
"workfeed": {
|
||||
"title": "Workfeed configuration",
|
||||
"subtitle": "Configuration of the Workfeed integration",
|
||||
"general_settings": "General settings",
|
||||
"general_settings_desc": "General settings for the Workfeed integration.",
|
||||
"enable": "Enable Workfeed",
|
||||
"enable_desc": "Enable or disable the Workfeed integration.",
|
||||
"api_settings": "API connection settings",
|
||||
"api_settings_desc": "The API connection settings used for Workfeed requests.",
|
||||
"api_url": "API URL",
|
||||
"api_url_desc": "The base URL for the Workfeed API.",
|
||||
"api_key": "API key",
|
||||
"api_key_desc": "The API key used to authenticate requests to Workfeed.",
|
||||
"close": "Close",
|
||||
"lookup": "Lookup",
|
||||
"validation_required": "This field is required.",
|
||||
"request_failed": "Request failed: {message}",
|
||||
"diagnostics": {
|
||||
"title": "Diagnostics",
|
||||
"description": "Run test lookups against the Workfeed integration endpoints.",
|
||||
"list_departments": "List departments",
|
||||
"list_employees": "List employees",
|
||||
"list_shifts": "List shifts",
|
||||
"get_employee": "Get employee by ID",
|
||||
"get_shift": "Get shift by ID",
|
||||
"prompt_search_optional": "Optional search text",
|
||||
"prompt_status_optional": "Optional shift status",
|
||||
"prompt_employee_id": "Employee ID",
|
||||
"prompt_shift_id": "Shift ID",
|
||||
"placeholder_search": "e.g. north facility",
|
||||
"placeholder_status": "e.g. published",
|
||||
"placeholder_employee_id": "e.g. emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
"placeholder_shift_id": "e.g. shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
"result_departments": "Department lookup result",
|
||||
"result_employees": "Employee lookup result",
|
||||
"result_shifts": "Shift lookup result",
|
||||
"result_employee": "Employee result",
|
||||
"result_shift": "Shift result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer_creation": {
|
||||
@@ -4227,4 +4267,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ import GuestBookExteriorWash from "@/views/guest/book/GuestBookExteriorWash.vue"
|
||||
import DepartmentProfile from "@/views/dashboards/superUserDashboard/department/DepartmentProfile.vue";
|
||||
import ConfigurationFXRatesAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationFXRatesAPI.vue";
|
||||
import ConfigurationWeatherAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationWeatherAPI.vue";
|
||||
import ConfigurationWorkfeed from "@/views/dashboards/superUserDashboard/configuration/ConfigurationWorkfeed.vue";
|
||||
import ConfigurationGatewayAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationGatewayAPI.vue";
|
||||
import UserWashSubscriptions from "@/views/dashboards/superUserDashboard/user/UserWashSubscriptions.vue";
|
||||
import UserProfile from "@/views/dashboards/userDashboard/profile/UserProfile.vue";
|
||||
@@ -816,6 +817,12 @@ export const router = createRouter({
|
||||
component: ConfigurationWeatherAPI,
|
||||
meta: { middleware: superUserMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'configurationWorkfeed',
|
||||
path: '/superuser/configuration/workfeed',
|
||||
component: ConfigurationWorkfeed,
|
||||
meta: { middleware: superUserMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'configurationGatewayAPI',
|
||||
path: '/superuser/configuration/gatewayapi',
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Swal from "sweetalert2";
|
||||
import PageTitle from "@/components/global/PageTitle.vue";
|
||||
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import ConfigurationSubPageWrapper
|
||||
from "@/views/dashboards/superUserDashboard/configuration/ConfigurationSubPageWrapper.vue";
|
||||
import ConfigurationCategory from "@/components/displays/superuser/configuration/ConfigurationCategory.vue";
|
||||
import ConfigurationSwitch from "@/components/displays/superuser/configuration/ConfigurationSwitch.vue";
|
||||
import ConfigurationSecretKey from "@/components/displays/superuser/configuration/ConfigurationSecretKey.vue";
|
||||
import ConfigurationInput from "@/components/displays/superuser/configuration/ConfigurationInput.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const module_config = ref([]);
|
||||
|
||||
const getModuleConfig = async () => {
|
||||
await SessionUser.superUser.modules.workfeed.config.get_all().then((response) => {
|
||||
const tmp_module_config = response?.data?.data ?? [];
|
||||
const entries = Array.isArray(tmp_module_config) ? tmp_module_config : Object.values(tmp_module_config);
|
||||
|
||||
module_config.value = entries.map((entry) => ({
|
||||
variable: entry.variable,
|
||||
value: entry.value,
|
||||
}));
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
const getModuleConfigValue = (variable) => {
|
||||
const config = module_config.value.find((entry) => entry.variable === variable);
|
||||
return config ? config.value : "";
|
||||
};
|
||||
|
||||
const showResult = (title, value) => {
|
||||
Swal.fire({
|
||||
title: title,
|
||||
html: "<pre>" + JSON.stringify(value, null, 2) + "</pre>",
|
||||
width: "60rem",
|
||||
confirmButtonText: t("configuration.workfeed.close"),
|
||||
});
|
||||
};
|
||||
|
||||
const toErrorMessage = (error) => {
|
||||
if (error?.response?.data?.message) {
|
||||
return error.response.data.message;
|
||||
}
|
||||
if (error?.response?.data?.data?.message) {
|
||||
return error.response.data.data.message;
|
||||
}
|
||||
if (error?.message) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
};
|
||||
|
||||
const showInputModal = async ({
|
||||
title,
|
||||
inputLabel,
|
||||
inputPlaceholder,
|
||||
inputValue = "",
|
||||
requireInput = false,
|
||||
run,
|
||||
resultTitle,
|
||||
}) => {
|
||||
return Swal.fire({
|
||||
title: title,
|
||||
input: "text",
|
||||
inputLabel: inputLabel,
|
||||
inputPlaceholder: inputPlaceholder,
|
||||
inputValue: inputValue,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("configuration.workfeed.lookup"),
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: async (input) => {
|
||||
const value = typeof input === "string" ? input.trim() : "";
|
||||
if (requireInput && value === "") {
|
||||
Swal.showValidationMessage(t("configuration.workfeed.validation_required"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return run(value).then((response) => response.data.data).catch((error) => {
|
||||
Swal.showValidationMessage(
|
||||
t("configuration.workfeed.request_failed", { message: toErrorMessage(error) })
|
||||
);
|
||||
});
|
||||
},
|
||||
allowOutsideClick: () => !Swal.isLoading()
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed || !result.value) {
|
||||
return;
|
||||
}
|
||||
showResult(resultTitle, result.value);
|
||||
});
|
||||
};
|
||||
|
||||
const onClickListDepartments = async () => {
|
||||
await showInputModal({
|
||||
title: t("configuration.workfeed.diagnostics.list_departments"),
|
||||
inputLabel: t("configuration.workfeed.diagnostics.prompt_search_optional"),
|
||||
inputPlaceholder: t("configuration.workfeed.diagnostics.placeholder_search"),
|
||||
run: (search) => SessionUser.superUser.modules.workfeed.functions.listDepartments({
|
||||
search: search || undefined,
|
||||
limit: 20,
|
||||
}),
|
||||
resultTitle: t("configuration.workfeed.diagnostics.result_departments"),
|
||||
});
|
||||
};
|
||||
|
||||
const onClickListEmployees = async () => {
|
||||
await showInputModal({
|
||||
title: t("configuration.workfeed.diagnostics.list_employees"),
|
||||
inputLabel: t("configuration.workfeed.diagnostics.prompt_search_optional"),
|
||||
inputPlaceholder: t("configuration.workfeed.diagnostics.placeholder_search"),
|
||||
run: (search) => SessionUser.superUser.modules.workfeed.functions.listEmployees({
|
||||
search: search || undefined,
|
||||
limit: 20,
|
||||
}),
|
||||
resultTitle: t("configuration.workfeed.diagnostics.result_employees"),
|
||||
});
|
||||
};
|
||||
|
||||
const onClickListShifts = async () => {
|
||||
await showInputModal({
|
||||
title: t("configuration.workfeed.diagnostics.list_shifts"),
|
||||
inputLabel: t("configuration.workfeed.diagnostics.prompt_status_optional"),
|
||||
inputPlaceholder: t("configuration.workfeed.diagnostics.placeholder_status"),
|
||||
run: (status) => SessionUser.superUser.modules.workfeed.functions.listShifts({
|
||||
status: status || undefined,
|
||||
limit: 20,
|
||||
}),
|
||||
resultTitle: t("configuration.workfeed.diagnostics.result_shifts"),
|
||||
});
|
||||
};
|
||||
|
||||
const onClickGetEmployee = async () => {
|
||||
await showInputModal({
|
||||
title: t("configuration.workfeed.diagnostics.get_employee"),
|
||||
inputLabel: t("configuration.workfeed.diagnostics.prompt_employee_id"),
|
||||
inputPlaceholder: t("configuration.workfeed.diagnostics.placeholder_employee_id"),
|
||||
requireInput: true,
|
||||
run: (id) => SessionUser.superUser.modules.workfeed.functions.getEmployee(id),
|
||||
resultTitle: t("configuration.workfeed.diagnostics.result_employee"),
|
||||
});
|
||||
};
|
||||
|
||||
const onClickGetShift = async () => {
|
||||
await showInputModal({
|
||||
title: t("configuration.workfeed.diagnostics.get_shift"),
|
||||
inputLabel: t("configuration.workfeed.diagnostics.prompt_shift_id"),
|
||||
inputPlaceholder: t("configuration.workfeed.diagnostics.placeholder_shift_id"),
|
||||
requireInput: true,
|
||||
run: (id) => SessionUser.superUser.modules.workfeed.functions.getShift(id),
|
||||
resultTitle: t("configuration.workfeed.diagnostics.result_shift"),
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getModuleConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessSuperUser()">
|
||||
<ConfigurationSubPageWrapper>
|
||||
<template #title>
|
||||
<PageTitle
|
||||
:title="$t('configuration.workfeed.title')"
|
||||
:subtitle="$t('configuration.workfeed.subtitle')"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="module_config.length > 0" #content>
|
||||
<ConfigurationCategory
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.general_settings')"
|
||||
:description="$t('configuration.workfeed.general_settings_desc')"
|
||||
icon="fas fa-users-cog"
|
||||
>
|
||||
<ConfigurationSwitch
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.enable')"
|
||||
:description="$t('configuration.workfeed.enable_desc')"
|
||||
icon="fas fa-users-cog"
|
||||
:value="getModuleConfigValue('enabled') === true"
|
||||
:on-switch="SessionUser.superUser.modules.workfeed.config.enabled.set"
|
||||
/>
|
||||
</ConfigurationCategory>
|
||||
|
||||
<ConfigurationCategory
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.api_settings')"
|
||||
:description="$t('configuration.workfeed.api_settings_desc')"
|
||||
icon="fas fa-key"
|
||||
>
|
||||
<ConfigurationInput
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.api_url')"
|
||||
:description="$t('configuration.workfeed.api_url_desc')"
|
||||
icon="fas fa-link"
|
||||
:value="getModuleConfigValue('api_url')"
|
||||
:on-save="SessionUser.superUser.modules.workfeed.config.keys.api_url.set"
|
||||
/>
|
||||
<ConfigurationSecretKey
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.api_key')"
|
||||
:description="$t('configuration.workfeed.api_key_desc')"
|
||||
icon="fas fa-key"
|
||||
:isSet="getModuleConfigValue('api_key') !== ''"
|
||||
:on-save="SessionUser.superUser.modules.workfeed.config.keys.api_key.set"
|
||||
/>
|
||||
</ConfigurationCategory>
|
||||
|
||||
<ConfigurationCategory
|
||||
class="mt-2"
|
||||
module="Workfeed"
|
||||
:title="$t('configuration.workfeed.diagnostics.title')"
|
||||
:description="$t('configuration.workfeed.diagnostics.description')"
|
||||
icon="fas fa-stethoscope"
|
||||
>
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="button is-dark"
|
||||
data-testid="workfeed-diagnostics-list-departments"
|
||||
@click="onClickListDepartments"
|
||||
>
|
||||
<span class="icon"><i class="fas fa-building"></i></span>
|
||||
<span>{{ $t("configuration.workfeed.diagnostics.list_departments") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-dark"
|
||||
data-testid="workfeed-diagnostics-list-employees"
|
||||
@click="onClickListEmployees"
|
||||
>
|
||||
<span class="icon"><i class="fas fa-users"></i></span>
|
||||
<span>{{ $t("configuration.workfeed.diagnostics.list_employees") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-dark"
|
||||
data-testid="workfeed-diagnostics-list-shifts"
|
||||
@click="onClickListShifts"
|
||||
>
|
||||
<span class="icon"><i class="fas fa-calendar-alt"></i></span>
|
||||
<span>{{ $t("configuration.workfeed.diagnostics.list_shifts") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-dark"
|
||||
data-testid="workfeed-diagnostics-get-employee"
|
||||
@click="onClickGetEmployee"
|
||||
>
|
||||
<span class="icon"><i class="fas fa-user"></i></span>
|
||||
<span>{{ $t("configuration.workfeed.diagnostics.get_employee") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-dark"
|
||||
data-testid="workfeed-diagnostics-get-shift"
|
||||
@click="onClickGetShift"
|
||||
>
|
||||
<span class="icon"><i class="fas fa-clock"></i></span>
|
||||
<span>{{ $t("configuration.workfeed.diagnostics.get_shift") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ConfigurationCategory>
|
||||
</template>
|
||||
</ConfigurationSubPageWrapper>
|
||||
</RestrictedPageWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
+1
@@ -16,6 +16,7 @@ const tabs = [
|
||||
{ name: 'Stripe', path: '/superuser/configuration/stripe' },
|
||||
{ name: 'FXRatesAPI', path: '/superuser/configuration/fxratesapi' },
|
||||
{ name: 'WeatherAPI', path: '/superuser/configuration/weatherapi' },
|
||||
{ name: 'Workfeed', path: '/superuser/configuration/workfeed' },
|
||||
{ name: 'GatewayAPI', path: '/superuser/configuration/gatewayapi' },
|
||||
{ name: 'XLVask', path: '/superuser/configuration/xlvask' },
|
||||
{ name: 'Entra', path: '/superuser/configuration/entra'},
|
||||
|
||||
@@ -353,6 +353,150 @@ export async function mockApi(page, options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/workfeed/config")) {
|
||||
const workfeedConfig = [
|
||||
{ module: "workfeed", variable: "enabled", type: "bool", value: true },
|
||||
{ module: "workfeed", variable: "api_url", type: "string", value: "https://api.workfeed.test" },
|
||||
{ module: "workfeed", variable: "api_key", type: "string", value: "test-api-key" },
|
||||
];
|
||||
|
||||
if (method === "GET") {
|
||||
const variable = parsedUrl.searchParams.get("variable");
|
||||
if (variable) {
|
||||
const match = workfeedConfig.find((entry) => entry.variable === variable);
|
||||
await route.fulfill(json({ data: match ? match.value : null }));
|
||||
return;
|
||||
}
|
||||
await route.fulfill(json({ data: workfeedConfig }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "POST") {
|
||||
await route.fulfill(json({ data: { updated: true } }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/modules/workfeed/departments") && method === "GET") {
|
||||
const search = (parsedUrl.searchParams.get("search") || "").toLowerCase();
|
||||
const departments = [
|
||||
{
|
||||
id: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3",
|
||||
name: "North Facility",
|
||||
timezone: "Europe/Copenhagen",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N4",
|
||||
name: "South Facility",
|
||||
timezone: "Europe/Copenhagen",
|
||||
active: true,
|
||||
},
|
||||
];
|
||||
const filtered = search
|
||||
? departments.filter((entry) => entry.name.toLowerCase().includes(search))
|
||||
: departments;
|
||||
|
||||
await route.fulfill(json({
|
||||
data: {
|
||||
items: filtered,
|
||||
pagination: { cursor: null, nextCursor: null, limit: 20, total: filtered.length },
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/modules/workfeed/employees") && method === "GET") {
|
||||
const search = (parsedUrl.searchParams.get("search") || "").toLowerCase();
|
||||
const employees = [
|
||||
{
|
||||
id: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
fullName: "Anne Nielsen",
|
||||
email: "anne.nielsen@example.com",
|
||||
departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5Q",
|
||||
fullName: "Mads Jensen",
|
||||
email: "mads.jensen@example.com",
|
||||
departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N4",
|
||||
active: true,
|
||||
},
|
||||
];
|
||||
const filtered = search
|
||||
? employees.filter((entry) => entry.fullName.toLowerCase().includes(search))
|
||||
: employees;
|
||||
|
||||
await route.fulfill(json({
|
||||
data: {
|
||||
items: filtered,
|
||||
pagination: { cursor: null, nextCursor: null, limit: 20, total: filtered.length },
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (/\/modules\/workfeed\/employees\/[^/]+$/i.test(pathname) && method === "GET") {
|
||||
const id = pathname.split("/").pop();
|
||||
await route.fulfill(json({
|
||||
data: {
|
||||
id,
|
||||
fullName: "Anne Nielsen",
|
||||
email: "anne.nielsen@example.com",
|
||||
departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3",
|
||||
active: true,
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/modules/workfeed/shifts") && method === "GET") {
|
||||
const status = (parsedUrl.searchParams.get("status") || "").toLowerCase();
|
||||
const shifts = [
|
||||
{
|
||||
id: "shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
title: "Morning Shift",
|
||||
status: "published",
|
||||
employeeId: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
startAt: "2026-03-24T06:00:00Z",
|
||||
endAt: "2026-03-24T14:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "shf_01J5P3X9D35R9C6BDZ1S0R4V6R",
|
||||
title: "Evening Shift",
|
||||
status: "draft",
|
||||
employeeId: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5Q",
|
||||
startAt: "2026-03-24T14:00:00Z",
|
||||
endAt: "2026-03-24T22:00:00Z",
|
||||
},
|
||||
];
|
||||
const filtered = status ? shifts.filter((entry) => entry.status.toLowerCase() === status) : shifts;
|
||||
|
||||
await route.fulfill(json({
|
||||
data: {
|
||||
items: filtered,
|
||||
pagination: { cursor: null, nextCursor: null, limit: 20, total: filtered.length },
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (/\/modules\/workfeed\/shifts\/[^/]+$/i.test(pathname) && method === "GET") {
|
||||
const id = pathname.split("/").pop();
|
||||
await route.fulfill(json({
|
||||
data: {
|
||||
id,
|
||||
title: "Morning Shift",
|
||||
status: "published",
|
||||
employeeId: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
startAt: "2026-03-24T06:00:00Z",
|
||||
endAt: "2026-03-24T14:00:00Z",
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (selfServe) {
|
||||
if (pathname.endsWith("/guest/departments") && method === "GET") {
|
||||
await route.fulfill(json({ data: selfServe.departments }));
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
|
||||
async function primeSuperuserSession(page) {
|
||||
const token = "superuser-workfeed-token";
|
||||
await seedAuthenticatedState(page, token);
|
||||
await page.goto("/login");
|
||||
await page.evaluate(async (sessionToken) => {
|
||||
const sessionModule = await import("/src/components/session/token/SessionUser.vue");
|
||||
window.localStorage.setItem("token", sessionToken);
|
||||
sessionModule.SessionUser.token.value = sessionToken;
|
||||
sessionModule.SessionUser.authenticated.value = true;
|
||||
sessionModule.SessionUser.permissions.value = ["superuser", "user"];
|
||||
sessionModule.SessionUser.initiated.value = true;
|
||||
}, token);
|
||||
}
|
||||
|
||||
async function runDiagnostic(page, testId, inputValue = "") {
|
||||
await page.locator(`[data-testid="${testId}"]:visible`).first().click();
|
||||
await expect(page.locator(".swal2-popup")).toBeVisible();
|
||||
const input = page.locator(".swal2-input");
|
||||
if (await input.isVisible()) {
|
||||
await input.fill(inputValue);
|
||||
}
|
||||
await page.locator(".swal2-confirm").click();
|
||||
await expect(page.locator(".swal2-popup pre")).toBeVisible();
|
||||
}
|
||||
|
||||
async function closeResultModal(page) {
|
||||
await page.locator(".swal2-confirm").click();
|
||||
await expect(page.locator(".swal2-popup")).toBeHidden();
|
||||
}
|
||||
|
||||
async function expandCategory(page, title) {
|
||||
await page.locator(".card-header.is-clickable:visible").filter({ hasText: title }).first().click();
|
||||
}
|
||||
|
||||
test.describe("Workfeed configuration smoke", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["superuser", "user"],
|
||||
});
|
||||
await primeSuperuserSession(page);
|
||||
});
|
||||
|
||||
test("@smoke renders workfeed config page and runs diagnostics", async ({ page }) => {
|
||||
await page.goto("/superuser/configuration/workfeed");
|
||||
|
||||
await expect(page).toHaveURL(/\/superuser\/configuration\/workfeed$/);
|
||||
await expect(page.locator("body")).toContainText("Workfeed configuration");
|
||||
await expandCategory(page, "General settings");
|
||||
await expect(page.locator("body")).toContainText("Enable Workfeed");
|
||||
|
||||
await expandCategory(page, "Diagnostics");
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-list-departments"]:visible').first()).toBeVisible();
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-list-employees"]:visible').first()).toBeVisible();
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-list-shifts"]:visible').first()).toBeVisible();
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-get-employee"]:visible').first()).toBeVisible();
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-get-shift"]:visible').first()).toBeVisible();
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-departments");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("North Facility");
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-employees", "anne");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Anne Nielsen");
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-shifts", "published");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Morning Shift");
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-employee", "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Anne Nielsen");
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-shift", "shf_01J5P3X9D35R9C6BDZ1S0R4V6Q");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Morning Shift");
|
||||
await closeResultModal(page);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const root = process.cwd();
|
||||
|
||||
const locales = {
|
||||
da: JSON.parse(readFileSync(join(root, "src/i18n/locales/da.json"), "utf8")),
|
||||
en: JSON.parse(readFileSync(join(root, "src/i18n/locales/en.json"), "utf8")),
|
||||
sv: JSON.parse(readFileSync(join(root, "src/i18n/locales/sv.json"), "utf8")),
|
||||
de: JSON.parse(readFileSync(join(root, "src/i18n/locales/de.json"), "utf8")),
|
||||
no: JSON.parse(readFileSync(join(root, "src/i18n/locales/no.json"), "utf8")),
|
||||
};
|
||||
|
||||
const flattenKeys = (value, prefix = "") => {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
||||
return [prefix];
|
||||
}
|
||||
|
||||
const keys = [];
|
||||
for (const [key, nestedValue] of Object.entries(value)) {
|
||||
const nestedPrefix = prefix ? `${prefix}.${key}` : key;
|
||||
keys.push(...flattenKeys(nestedValue, nestedPrefix));
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
describe("workfeed i18n coverage", () => {
|
||||
it("defines configuration.workfeed namespace in every active locale", () => {
|
||||
for (const [locale, data] of Object.entries(locales)) {
|
||||
expect(data.configuration?.workfeed, `missing configuration.workfeed in ${locale}`).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the same workfeed key set across locales", () => {
|
||||
const baselineKeys = flattenKeys(locales.en.configuration.workfeed).sort();
|
||||
|
||||
for (const [locale, data] of Object.entries(locales)) {
|
||||
const localeKeys = flattenKeys(data.configuration.workfeed).sort();
|
||||
expect(localeKeys, `mismatched key set in ${locale}`).toEqual(baselineKeys);
|
||||
}
|
||||
});
|
||||
|
||||
it("contains required keys used by configuration view", () => {
|
||||
const requiredKeys = [
|
||||
"title",
|
||||
"subtitle",
|
||||
"general_settings",
|
||||
"enable",
|
||||
"api_settings",
|
||||
"api_url",
|
||||
"api_key",
|
||||
"close",
|
||||
"lookup",
|
||||
"validation_required",
|
||||
"request_failed",
|
||||
"diagnostics.title",
|
||||
"diagnostics.description",
|
||||
"diagnostics.list_departments",
|
||||
"diagnostics.list_employees",
|
||||
"diagnostics.list_shifts",
|
||||
"diagnostics.get_employee",
|
||||
"diagnostics.get_shift",
|
||||
"diagnostics.prompt_search_optional",
|
||||
"diagnostics.prompt_status_optional",
|
||||
"diagnostics.prompt_employee_id",
|
||||
"diagnostics.prompt_shift_id",
|
||||
"diagnostics.result_departments",
|
||||
"diagnostics.result_employees",
|
||||
"diagnostics.result_shifts",
|
||||
"diagnostics.result_employee",
|
||||
"diagnostics.result_shift",
|
||||
];
|
||||
|
||||
const availableKeys = new Set(flattenKeys(locales.en.configuration.workfeed));
|
||||
requiredKeys.forEach((key) => {
|
||||
expect(availableKeys.has(key)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const root = process.cwd();
|
||||
const workfeedModuleSource = readFileSync(
|
||||
join(root, "src/components/session/token/superUser/modules/workfeed/Workfeed.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const workfeedConfigSource = readFileSync(
|
||||
join(root, "src/components/session/token/superUser/modules/workfeed/Config.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const workfeedPageSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/configuration/ConfigurationWorkfeed.vue"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
describe("workfeed module contract", () => {
|
||||
it("defines module metadata and config endpoint", () => {
|
||||
expect(workfeedModuleSource).toContain('title: "Workfeed"');
|
||||
expect(workfeedModuleSource).toContain('endpoint: "/modules/workfeed"');
|
||||
expect(workfeedModuleSource).toContain('config_endpoint: "/configuration/workfeed"');
|
||||
});
|
||||
|
||||
it("defines list and get endpoint wrappers for employees, shifts, and departments", () => {
|
||||
expect(workfeedModuleSource).toContain('"/modules/workfeed/employees"');
|
||||
expect(workfeedModuleSource).toContain('"/modules/workfeed/employees/" + encodeURIComponent(id)');
|
||||
expect(workfeedModuleSource).toContain('"/modules/workfeed/shifts"');
|
||||
expect(workfeedModuleSource).toContain('"/modules/workfeed/shifts/" + encodeURIComponent(id)');
|
||||
expect(workfeedModuleSource).toContain('"/modules/workfeed/departments"');
|
||||
});
|
||||
|
||||
it("supports all openapi filter parameters for list endpoints", () => {
|
||||
expect(workfeedModuleSource).toContain("search");
|
||||
expect(workfeedModuleSource).toContain("departmentId");
|
||||
expect(workfeedModuleSource).toContain("includeInactive");
|
||||
expect(workfeedModuleSource).toContain("employeeId");
|
||||
expect(workfeedModuleSource).toContain("status");
|
||||
expect(workfeedModuleSource).toContain("from");
|
||||
expect(workfeedModuleSource).toContain("to");
|
||||
expect(workfeedModuleSource).toContain("limit");
|
||||
expect(workfeedModuleSource).toContain("cursor");
|
||||
expect(workfeedModuleSource).toContain("sanitizeQuery");
|
||||
});
|
||||
});
|
||||
|
||||
describe("workfeed config contract", () => {
|
||||
it("uses workfeed config endpoints", () => {
|
||||
expect(workfeedConfigSource).toContain('"/workfeed/config?variable=" + variable');
|
||||
expect(workfeedConfigSource).toContain('"/workfeed/config"');
|
||||
});
|
||||
|
||||
it("supports openapi config variables", () => {
|
||||
expect(workfeedConfigSource).toContain('Config.get("enabled")');
|
||||
expect(workfeedConfigSource).toContain('Config.get("api_url")');
|
||||
expect(workfeedConfigSource).toContain('Config.get("api_key")');
|
||||
expect(workfeedConfigSource).toContain('Config.set("enabled", enabled)');
|
||||
expect(workfeedConfigSource).toContain('Config.set("api_url", value)');
|
||||
expect(workfeedConfigSource).toContain('Config.set("api_key", value)');
|
||||
});
|
||||
});
|
||||
|
||||
describe("workfeed config page contract", () => {
|
||||
it("uses i18n keys and module bindings for controls", () => {
|
||||
expect(workfeedPageSource).toContain("$t('configuration.workfeed.title')");
|
||||
expect(workfeedPageSource).toContain("$t('configuration.workfeed.subtitle')");
|
||||
expect(workfeedPageSource).toContain("$t('configuration.workfeed.enable')");
|
||||
expect(workfeedPageSource).toContain("$t('configuration.workfeed.api_url')");
|
||||
expect(workfeedPageSource).toContain("$t('configuration.workfeed.api_key')");
|
||||
expect(workfeedPageSource).toContain(
|
||||
":on-switch=\"SessionUser.superUser.modules.workfeed.config.enabled.set\""
|
||||
);
|
||||
expect(workfeedPageSource).toContain(
|
||||
":on-save=\"SessionUser.superUser.modules.workfeed.config.keys.api_url.set\""
|
||||
);
|
||||
expect(workfeedPageSource).toContain(
|
||||
":on-save=\"SessionUser.superUser.modules.workfeed.config.keys.api_key.set\""
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes modal diagnostic actions for each workfeed endpoint family", () => {
|
||||
expect(workfeedPageSource).toContain("onClickListDepartments");
|
||||
expect(workfeedPageSource).toContain("onClickListEmployees");
|
||||
expect(workfeedPageSource).toContain("onClickListShifts");
|
||||
expect(workfeedPageSource).toContain("onClickGetEmployee");
|
||||
expect(workfeedPageSource).toContain("onClickGetShift");
|
||||
expect(workfeedPageSource).toContain("workfeed-diagnostics-list-departments");
|
||||
expect(workfeedPageSource).toContain("workfeed-diagnostics-list-employees");
|
||||
expect(workfeedPageSource).toContain("workfeed-diagnostics-list-shifts");
|
||||
expect(workfeedPageSource).toContain("workfeed-diagnostics-get-employee");
|
||||
expect(workfeedPageSource).toContain("workfeed-diagnostics-get-shift");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const root = process.cwd();
|
||||
const superUserObjectSource = readFileSync(
|
||||
join(root, "src/components/session/token/superUserObject.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const routerSource = readFileSync(join(root, "src/router.js"), "utf8");
|
||||
const navItemsSource = readFileSync(
|
||||
join(root, "src/components/models/navigation/items/NavigationMenuItemsSuperUser.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const leftMenuSource = readFileSync(
|
||||
join(root, "src/components/menus/superuser/SuperUserLeftMenu.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const configTabsSource = readFileSync(
|
||||
join(
|
||||
root,
|
||||
"src/views/dashboards/superUserDashboard/configuration/SuperUserDashboardConfigurationNavigation.vue"
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
describe("workfeed superuser wiring", () => {
|
||||
it("is registered in SessionUser superuser modules", () => {
|
||||
expect(superUserObjectSource).toContain(
|
||||
'import { Workfeed } from "@/components/session/token/superUser/modules/workfeed/Workfeed.vue";'
|
||||
);
|
||||
expect(superUserObjectSource).toContain("get workfeed() { return Workfeed; },");
|
||||
});
|
||||
|
||||
it("has a dedicated superuser configuration route", () => {
|
||||
expect(routerSource).toContain(
|
||||
'import ConfigurationWorkfeed from "@/views/dashboards/superUserDashboard/configuration/ConfigurationWorkfeed.vue";'
|
||||
);
|
||||
expect(routerSource).toContain("name: 'configurationWorkfeed'");
|
||||
expect(routerSource).toContain("path: '/superuser/configuration/workfeed'");
|
||||
expect(routerSource).toContain("component: ConfigurationWorkfeed");
|
||||
});
|
||||
|
||||
it("appears in superuser configuration navigation menus", () => {
|
||||
expect(navItemsSource).toContain(
|
||||
"SessionUser.superUser.modules.workfeed.meta.labels.multiple"
|
||||
);
|
||||
expect(navItemsSource).toContain("to: '/superuser/configuration/workfeed'");
|
||||
|
||||
expect(leftMenuSource).toContain("SessionUser.superUser.modules.workfeed.meta.title");
|
||||
expect(leftMenuSource).toContain(
|
||||
"SessionUser.superUser.modules.workfeed.meta.config_endpoint"
|
||||
);
|
||||
|
||||
expect(configTabsSource).toContain("{ name: 'Workfeed', path: '/superuser/configuration/workfeed' }");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user