Skip to content

Commit

Permalink
Merge pull request #6 from meta-d/develop
Browse files Browse the repository at this point in the history
Merge develop to main
  • Loading branch information
anypossiblew authored Sep 18, 2023
2 parents 0013973 + 00fc0ea commit b18c5c6
Show file tree
Hide file tree
Showing 258 changed files with 10,775 additions and 4,652 deletions.
1 change: 1 addition & 0 deletions .deploy/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@angular/elements": "16.1.7",
"@angular/forms": "16.1.7",
"@angular/material": "16.1.6",
"@angular/material-date-fns-adapter": "^16.1.6",
"@angular/platform-browser": "16.1.7",
"@angular/platform-browser-dynamic": "16.1.7",
"@angular/router": "16.1.7",
Expand Down
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
English | [中文](./README_zh.md)

# Metad Analytics Platform

[uri_metad]: https://mtda.cloud/en/
[uri_license]: https://www.gnu.org/licenses/agpl-3.0.html
[uri_license_image]: https://img.shields.io/badge/License-AGPL%20v3-blue.svg

![visitors](https://visitor-badge.laobi.icu/badge?page_id=meta-d.ocap)
[![License: AGPL v3][uri_license_image]][uri_license]
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/meta-d/ocap)
<p align="center">
<a href="https://mtda.cloud/en/">
<img src="https://avatars.githubusercontent.com/u/100019674?v=4" alt="Metad">
</a>
</p>
<p align="center">
<em>Open-Source Analytics Platform for Enterprise Data Analysis, Indicator Management and Reporting</em>
</p>
<p align="center">
<a href="https://github.com/meta-d/ocap/" target="_blank">
<img src="https://visitor-badge.laobi.icu/badge?page_id=meta-d.ocap" alt="Visitors">
</a>
<a href="https://github.com/meta-d/ocap/releases" target="_blank">
<img src="https://img.shields.io/github/v/release/meta-d/ocap?color=white" alt="Release">
</a>
<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">
<img src="https://img.shields.io/badge/License-AGPL%20v3-blue.svg" alt="License: AGPL v3">
</a>
<a href="https://gitpod.io/#https://github.com/meta-d/ocap" target="_blank">
<img src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod" alt="Gitpod Ready-to-Code">
</a>
</p>

# Metad Analytics Platform

## 💡 What's New

We released new version which includes [AI Copilot](https://mtda.cloud/en/blog/copilot-1-assist-data-query) in query lab, it can assit you to write and optimize SQL or MDX queries.

## 🌟 What is it

[Metad Platform][uri_metad] - **Open-Source Analytics Platform** for Enterprise Data Analysis and Reporting.
[Metad Platform][uri_metad] - **Open-Source Analytics Platform** for Enterprise Data Analysis Indicator Management and Reporting.

* **Semantic Model**: Perform multi-dimensional data modeling and analysis, allowing users to explore data from various dimensions and hierarchies.
* **Story Dashboard**: Create compelling visual narratives with Story Dashboards, combining interactive visualizations, narrative elements, and data-driven storytelling.
* **Indicator Management**: Easily define, manage, and monitor key performance indicators (KPIs) to ensure data quality, consistency, and effective performance analysis.
* **AI Copilot**: Benefit from AI-driven insights and recommendations to enhance decision-making processes and identify actionable opportunities.

![Story Workspace](https://github.com/meta-d/meta-d/blob/main/img/story-workspace.png)
![Story Workspace](https://github.com/meta-d/meta-d/blob/main/img/en/story-workspace.png)

![Indicator Application](https://github.com/meta-d/meta-d/blob/main/img/indicator-application.png)
![Indicator Application](https://github.com/meta-d/meta-d/blob/main/img/en/indicator-application.png)

## ✨ Features

Expand Down Expand Up @@ -81,9 +100,8 @@ Basic feartures of the platform:
### Sales overview [open in new tab](https://app.mtda.cloud/public/story/892690e5-66ab-4649-9bf5-c1a9c432c01b)
![Sales overview Screenshot](https://github.com/meta-d/meta-d/blob/main/img/adv-sales-overview.png)


### Pareto analysis [open in new tab](https://app.mtda.cloud/public/story/892690e5-66ab-4649-9bf5-c1a9c432c01b?pageKey=bsZ0sjxnxI)
![Pareto analysis Screenshot](https://github.com/meta-d/meta-d/blob/main/img/product-pareto-analysis.png)
![Pareto analysis Screenshot](https://github.com/meta-d/meta-d/blob/main/img/en/story-pareto-analysis.png)

### Product profit analysis [open in new tab](https://app.mtda.cloud/public/story/892690e5-66ab-4649-9bf5-c1a9c432c01b?pageKey=6S4oEUnVO3)
![Product profit analysis Screenshot](https://github.com/meta-d/meta-d/blob/main/img/profit-margin-analysis.jpg)
Expand All @@ -95,7 +113,7 @@ Basic feartures of the platform:
![Bigview dashboard Screenshot](https://github.com/meta-d/meta-d/blob/main/img/bigview-supermart-sales.png)

### Indicator application [open in new tab](https://www.mtda.cloud/en/blog/2023/07/24/sample-adv-7-indicator-app)
![Indicator application Screenshot](https://github.com/meta-d/meta-d/blob/main/img/indicator-application.png)
![Indicator application Screenshot](https://github.com/meta-d/meta-d/blob/main/img/en/indicator-application.png)

### Indicator mobile app [open in new tab](https://www.mtda.cloud/en/blog/2023/07/24/sample-adv-7-indicator-app)
![Indicator mobile app Screenshot](https://github.com/meta-d/meta-d/blob/main/img/indicator-app-mobile.jpg)
Expand Down
46 changes: 1 addition & 45 deletions apps/cloud/src/app/@core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,14 @@ import {
import { FormsModule } from '@angular/forms'
import {
MissingTranslationHandler,
MissingTranslationHandlerParams,
TranslateLoader,
TranslateModule
} from '@ngx-translate/core'
import { TranslateHttpLoader } from '@ngx-translate/http-loader'
import { PACStateModule } from '@metad/cloud/state'
import { ZhHans } from '@metad/ocap-angular/i18n'
import { zhHans as CoreZhHans } from '@metad/core'
import { ZhHans as StoryZhHans } from '@metad/story/i18n'
import { ZhHans as AuthZhHans } from '@metad/cloud/auth'
import { ZhHans as IAppZhHans } from '@metad/cloud/indicator-market/i18n'
import { map, Observable } from 'rxjs'
import { AuthModule } from './auth/auth.module'
import { throwIfAlreadyLoaded } from './module-import-guard'
import { HttpLoaderFactory, MyMissingTranslationHandler } from './theme'

class CustomTranslateHttpLoader extends TranslateHttpLoader {
getTranslation(lang: string): Observable<Object> {
let ocapTranslates = {}
switch(lang) {
case 'zh-Hans':
case 'zh-CN':
ocapTranslates = {
...ZhHans,
...CoreZhHans,
...StoryZhHans,
...AuthZhHans,
...IAppZhHans
}
break
default:
}
return super.getTranslation(lang).pipe(
map((t) => ({
...t,
...ocapTranslates
}))
)
}
}

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new CustomTranslateHttpLoader(http, `./assets/i18n/`, '.json')
}

export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) {
if (params.interpolateParams) {
return params.interpolateParams['Default'] || params.key
}
return params.key
}
}

@NgModule({
imports: [
Expand Down
63 changes: 23 additions & 40 deletions apps/cloud/src/app/@core/guards/invite.guard.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,25 @@
import { Store } from './../services/store.service';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { first } from 'rxjs/operators';
import { PermissionsEnum } from '@metad/contracts';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { inject } from '@angular/core'
import { ActivatedRouteSnapshot, Router } from '@angular/router'
import { PermissionsEnum } from '@metad/contracts'
import { combineLatest } from 'rxjs'
import { first, map, tap } from 'rxjs/operators'
import { Store } from './../services/store.service'

@UntilDestroy()
@Injectable()
export class InviteGuard {
hasPermission = false;
organizationInvitesAllowed = false;

constructor(
private readonly router: Router,
private readonly store: Store
) {}

async canActivate(route: ActivatedRouteSnapshot) {
const expectedPermissions: PermissionsEnum[] =
route.data.expectedPermissions;
this.store.userRolePermissions$.pipe(first()).subscribe(() => {
this.hasPermission = expectedPermissions.some((permission) =>
this.store.hasPermission(permission)
);
});
this.store.selectedOrganization$
.pipe(first(), untilDestroyed(this))
.subscribe((organization) => {
if (organization) {
this.organizationInvitesAllowed =
organization.invitesAllowed;
}
});
if (this.organizationInvitesAllowed && this.hasPermission) {
return true;
}

this.router.navigate(['/']);
return false;
}
export function inviteGuard(route: ActivatedRouteSnapshot) {
const store = inject(Store)
const router = inject(Router)
const expectedPermissions: PermissionsEnum[] = route.data.expectedPermissions
return combineLatest([
store.userRolePermissions$.pipe(
first(),
map(() => expectedPermissions.some((permission) => store.hasPermission(permission)))
),
store.selectedOrganization$.pipe(
first(),
map((organization) => organization?.invitesAllowed)
)
]).pipe(
map(([hasPermission, invitesAllowed]) => invitesAllowed && hasPermission),
tap((allowed) => allowed || router.navigate(['/']))
)
}
1 change: 1 addition & 0 deletions apps/cloud/src/app/@core/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './selector.service'
export * from './theme.service'
export * from './icons'
export * from './translate'
85 changes: 85 additions & 0 deletions apps/cloud/src/app/@core/theme/translate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { HttpClient } from '@angular/common/http'
import en from '@angular/common/locales/en'
import localeZhExtra from '@angular/common/locales/extra/zh-Hans'
import zh from '@angular/common/locales/zh'
import localeZh from '@angular/common/locales/zh-Hans'
import { ZhHans as AuthZhHans, ZhHant as AuthZhHant } from '@metad/cloud/auth'
import { ZhHans as IAppZhHans, ZhHant as IAppZhHant } from '@metad/cloud/indicator-market/i18n'
import { registerLocaleData as nxRegisterLocaleData, zhHans as CoreZhHans, zhHant as CoreZhHant } from '@metad/core'
import { ZhHans, ZhHant } from '@metad/ocap-angular/i18n'
import { ZhHans as StoryZhHans, ZhHant as StoryZhHant } from '@metad/story/i18n'
import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core'
import { TranslateHttpLoader } from '@ngx-translate/http-loader'
import { } from '@metad/core'
import { Observable, map } from 'rxjs'
import { LanguagesEnum } from '../types'
import { registerLocaleData } from '@angular/common'
import { enUS, zhCN, zhHK } from 'date-fns/locale'


export const LOCALE_DEFAULT = LanguagesEnum.SimplifiedChinese
registerLocaleData(localeZh, LOCALE_DEFAULT, localeZhExtra)
registerLocaleData(zh)
registerLocaleData(en)
nxRegisterLocaleData(CoreZhHans, LanguagesEnum.SimplifiedChinese)
nxRegisterLocaleData(CoreZhHant, LanguagesEnum.TraditionalChinese)

class CustomTranslateHttpLoader extends TranslateHttpLoader {
getTranslation(lang: string): Observable<Object> {
let ocapTranslates = {}
switch (lang) {
case LanguagesEnum.Chinese:
case LanguagesEnum.SimplifiedChinese:
ocapTranslates = {
...ZhHans,
...CoreZhHans,
...StoryZhHans,
...AuthZhHans,
...IAppZhHans
}
break
case LanguagesEnum.TraditionalChinese:
ocapTranslates = {
...ZhHant,
...CoreZhHant,
...StoryZhHant,
...AuthZhHant,
...IAppZhHant
}
break
default:
}
return super.getTranslation(lang).pipe(
map((t) => ({
...t,
...ocapTranslates
}))
)
}
}

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new CustomTranslateHttpLoader(http, `./assets/i18n/`, '.json')
}

export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) {
if (params.interpolateParams) {
return params.interpolateParams['Default'] || params.key
}
return params.key
}
}

export function mapDateLocale(locale: string) {
switch (locale) {
case 'zh-CN':
case 'zh-Hans':
case 'zh':
return zhCN
case 'zh-Hant':
return zhHK
default:
return enUS
}
}
10 changes: 1 addition & 9 deletions apps/cloud/src/app/@core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,10 @@ export enum RequestMethodEnum {

export const LANGUAGES = [
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-Hant', label: '繁体中文' },
{ value: 'en', label: 'English' }
]

// /**
// * @deprecated use in state project
// */
// export enum ThemesEnum {
// default = 'default',
// dark = 'dark',
// thin = 'thin'
// }

export interface Group {
id: string
name: string
Expand Down
2 changes: 2 additions & 0 deletions apps/cloud/src/app/@shared/directives/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './lazy-img.directive'
export * from './manage-entity-base'
52 changes: 52 additions & 0 deletions apps/cloud/src/app/@shared/directives/manage-entity-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { effect, inject, signal } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { IBasePerTenantEntityModel } from '../../@core'
import { TranslationBaseComponent } from '../language/translation-base.component'

/**
* Extends this class to use manage entity methods
*/
export abstract class ManageEntityBaseComponent<
T extends IBasePerTenantEntityModel = IBasePerTenantEntityModel
> extends TranslationBaseComponent {
protected readonly router = inject(Router)
protected readonly route = inject(ActivatedRoute)

openedLinks = signal<T[]>([])
currentLink = signal<T | null>(null)

constructor() {
super()

effect(
() => {
if (this.currentLink()) {
const links = this.openedLinks()
const index = links.findIndex((item) => item.id === this.currentLink().id)
if (index > -1) {
if (links[index] !== this.currentLink()) {
this.openedLinks.set([...links.slice(0, index), this.currentLink(), ...links.slice(index + 1)])
}
} else {
this.openedLinks.set([...links, this.currentLink()])
}
}
},
{ allowSignalWrites: true }
)
}

trackByEntityId(index: number, item: T) {
return item?.id
}

setCurrentLink(link: T) {
this.currentLink.set(link)
}

removeOpenedLink(link: T) {
this.currentLink.set(null)
this.openedLinks.set(this.openedLinks().filter((item) => item.id !== link.id))
this.router.navigate(['.'], { relativeTo: this.route })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
@apply w-full;
}
Loading

0 comments on commit b18c5c6

Please sign in to comment.