Skip to content

Commit

Permalink
Merge branch 'main' into cme-feature/attribute-manipulation-package-l…
Browse files Browse the repository at this point in the history
…ock-fix

Resolve merge conflicts
  • Loading branch information
RadStr committed Jan 30, 2025
2 parents cccc0e6 + 70a5322 commit b3ed4db
Show file tree
Hide file tree
Showing 75 changed files with 1,203 additions and 1,289 deletions.
5 changes: 2 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**/*.
**/.*
documentation
**/node_modules
schemas
Expand All @@ -7,5 +7,4 @@ LICENSE
README.md
!.npmrc
services/backend/database
**/dist
**/*.md
**/dist
11 changes: 7 additions & 4 deletions .github/workflows/docker-ws.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name: Create and publish a Docker image
name: Docker image

on:
push:
branches:
- main
- stable
tags:
- "v*"

env:
REGISTRY: ghcr.io
Expand Down Expand Up @@ -32,6 +33,8 @@ jobs:
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Get commit metadata
id: commit_meta
run: |
Expand All @@ -44,12 +47,12 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker-ws/Dockerfile
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT=${{ github.sha }}
GIT_REF=${{ github.ref }}
GIT_COMMIT_DATE=${{ env.COMMIT_DATE }}
GIT_COMMIT_NUMBER=${{ env.COMMIT_NUMBER }}
GIT_COMMIT_NUMBER=${{ env.COMMIT_NUMBER }}
5 changes: 3 additions & 2 deletions docker-ws/Dockerfile → Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ RUN --mount=type=cache,target=/root/.npm npm i -g prisma && \
chmod -R a+rwx /usr/local/lib/node_modules/prisma

# Configure nginx
COPY --chmod=777 ./docker-ws/nginx.conf /etc/nginx/nginx.conf-template
COPY --chmod=777 ./docker/ws/nginx.conf /etc/nginx/nginx.conf-template
RUN chmod -R a+rwx /etc/nginx/

COPY --chmod=777 ./docker-ws/docker-entrypoint.sh ./docker-ws/docker-healthcheck.sh .
COPY --chmod=777 ./docker/ws/docker-entrypoint.sh ./docker/ws/docker-healthcheck.sh .

RUN chmod a+rwx /usr/share/nginx/html && mkdir /usr/src/app/database && chmod a+rwx /usr/src/app/database

Expand All @@ -62,6 +62,7 @@ RUN mkdir -p /usr/src/app/database && \
# Copy frontend
COPY --from=builder /usr/src/app/.dist /usr/share/nginx/html-template

VOLUME /usr/src/app/database
EXPOSE 80
HEALTHCHECK CMD ./docker-healthcheck.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,38 @@ Check our website [dataspecer.com](https://dataspecer.com/) for more information

You can easily run the whole application in a Docker container.

Instructions are in [docker-ws directory](./docker-ws/README.md).
If you just want to try it out for a while and don't care where the data is stored, use following command and then go open [http://localhost:3000/](http://localhost:3000/).

```bash
docker run -it --rm -eBASE_URL=http://localhost:3000/ -p3000:80 ghcr.io/dataspecer/ws
```

---

- The container exposes port 80.
- Mount `/usr/src/app/database` directory to your local directory that will be filled with `database.db` file and `stores` directory. If the directory is empty, files would be created. **You need to mkdir the mounted directory with the correct user otherwise Docker will create it as root**.
- If you want to run the Dataspecer under specific user, use `USER` env variable with desired UID.
- You MUST specify full base URL using `BASE_URL` env. Some functionalities need to know the domain name and the relative subdirectory is also important. If you are using reverse proxy, it is expected that the base path *is preserved*. (For example, `https://example.com/dataspecer-instance-1/schema` should point to `http://localhost:3001/dataspecer-instance-1/schema`)

Use the following docker run command:
```bash
docker run -it --name dataspecer -eBASE_URL=http://localhost/ -eUSER=1000 -v ./database:/usr/src/app/database -p80:80 ghcr.io/dataspecer/ws
```

Or use following docker-compose file
```yaml
services:
dataspecer:
image: ghcr.io/dataspecer/ws
environment:
BASE_URL: http://localhost
USER: 1000
ports:
- 80:80 # Change the first number to your desired port
volumes:
- ./database:/usr/src/app/database
#- ./main.config.js:/usr/src/main.config.js # Backend configuration
```

## Documentation

Expand Down Expand Up @@ -38,4 +69,7 @@ Then
- Run `npm install` to install all external packages (including TypeScript for typechecking and Turborepo for building) and link all dependencies between local packages.
- Run `npm run build` to build everything. This will execute `turbo build` under the hood. This will build packages, which are necessary for the development of other packages and applications; and it also build applications themselves, which is not necessary for development (see the next step). (If you want to build only packages necessary for a specific package or application, use `npx turbo run build --filter=<package-name>`. Obtain the name of the package from `package.json` file.)

To develop a concrete package or application, there is *usually* an `npm run dev` script that will run live server, which updates everything. See individual packages for more details.
To develop a concrete package or application, there is *usually* an `npm run dev` script that will run live server, which updates everything. See individual packages for more details.

### Inside Docker

Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,44 @@ import { withAggregatedEntity } from "./utilities";
import { addRelatedEntitiesAction } from "./add-related-entities-to-visual-model";
import { ClassesContextType } from "../context/classes-context";
import { findPositionForNewNodesUsingLayouting } from "./layout-visual-model";
import { findSourceModelOfEntity } from "../service/model-service";
import { createLogger } from "../application";
import { getVisualNodeContentBasedOnExistingEntities } from "./add-semantic-attribute-to-visual-model";

const LOG = createLogger(import.meta.url);

export async function addSemanticClassProfileToVisualModelAction(
notifications: UseNotificationServiceWriterType,
graph: ModelGraphContextType,
classes: ClassesContextType,
visualModel: WritableVisualModel,
diagram: UseDiagramType,
entityIdentifier: string,
modelIdentifier: string,
// We ignore following property, as we may get invalid model.
// Sometimes, it is a model of the profiled entity not the profile.
_modelIdentifier: string,
position: { x: number, y: number } | null,
) {
const entities = graph.aggregatorView.getEntities();
if(position === null) {
position = (await findPositionForNewNodesUsingLayouting(notifications, diagram, graph, visualModel, classes, [entityIdentifier]))[entityIdentifier];
const positions = await findPositionForNewNodesUsingLayouting(
notifications, diagram, graph, visualModel, classes, [entityIdentifier]);
position = positions[entityIdentifier];
}

const model = findSourceModelOfEntity(entityIdentifier, graph.models);
if (model === null) {
LOG.error("Operation ignored, we fail to find model for given entity.", {entity: entityIdentifier});
notifications.error("Can not find model for given entity");
return;
}

withAggregatedEntity(notifications, entities,
entityIdentifier, modelIdentifier,
entityIdentifier, model.getId(),
isSemanticModelClassUsage, (entity) => {
addSemanticClassProfileToVisualModelCommand(
classes, visualModel, entity,
modelIdentifier, position);
model.getId(), position);
addRelatedEntitiesAction(
notifications, graph, visualModel, Object.values(entities),
graph.models, entity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export function addSemanticProfileToVisualModelAction(
profile: SemanticModelEntity,
modelIdentifier: string,
) {
const visualSource = visualModel.getVisualEntityForRepresented(profiled.id);
const visualTarget = visualModel.getVisualEntityForRepresented(profile.id);
const visualSource = visualModel.getVisualEntityForRepresented(profile.id);
const visualTarget = visualModel.getVisualEntityForRepresented(profiled.id);
if (visualSource === null || visualTarget === null) {
console.warn("Ignored request to add profile as ends are missing in visual model.",
{visualModel, profiled, profile});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,17 @@ export function addRelatedEntitiesAction(
}
if (isSemanticModelClassUsage(candidate)) {
if (shouldAddProfile(visualModel, identifier, candidate)) {
// "candidate" is profile of "identifier"
addSemanticProfileToVisualModelAction(
visualModel, entity, candidate, model.getId());
} else if (addingProfile && shouldAddProfile(visualModel, candidate.id, entity)) {
// "identifier" is profile of "candidate"
addSemanticProfileToVisualModelAction(
visualModel, candidate, entity, model.getId());
}
}
if (addingProfile && isSemanticModelClass(candidate)) {
// We are adding profile and the entity is a class.
// We are adding profile, candidate is a class, it could profiled class.
if (entity.usageOf === candidate.id) {
addSemanticProfileToVisualModelAction(
visualModel, candidate, entity, model.getId());
Expand Down Expand Up @@ -124,17 +129,17 @@ function shouldAddRelationshipUsage(

function shouldAddProfile(
visualModel: VisualModel,
identifier: string,
candidate: SemanticModelClassUsage,
profiled: string,
profile: SemanticModelClassUsage,
): boolean {
if (candidate.id === identifier) {
if (profile.id === profiled) {
// We do not support self profiles.
return false;
}
// The candidate may be specialization of what we are adding.
if (candidate.usageOf === identifier) {
if (profile.usageOf === profiled) {
// We return true if the other is in the visual model.
return visualModel.getVisualEntityForRepresented(candidate.id) !== null;
return visualModel.getVisualEntityForRepresented(profile.id) !== null;
}
return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function openCreateAssociationDialogAction(
}

const state = createCreateAssociationDialogState(
classes, graph, visualModel, options.language);
classes, graph, visualModel, options.language, model.getId());

const onConfirm = (state: EditAssociationDialogState) => {
createSemanticAssociation(notifications, visualModel, graph, state, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,6 @@ function openCreateAttributeDialog(
onConfirm: (state: EditAttributeDialogState) => void,
) {
const state = createNewAttributeDialogState(
classes, graph, visualModel, options.language);
classes, graph, visualModel, options.language, model.getId());
dialogs.openDialog(createNewAttributeDialog(state, onConfirm));
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function createAssociationToCreatedClass(
createdClassData: CreatedSemanticEntityData,
editClassDialogState: EditClassDialogState
) {
const defaultEditAssociationState = createCreateAssociationDialogState(classes, graph, visualModel, options.language);
const defaultEditAssociationState = createCreateAssociationDialogState(classes, graph, visualModel, options.language, null);

const node = visualModel.getVisualEntity(nodeIdentifier);
if(node === null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ function openCreateClassDialog(
onConfirm: (state: EditClassDialogState) => void,
) {
const state = createNewClassDialogState(
classes, graph, visualModel, options.language);
classes, graph, visualModel, options.language, model.getId());
dialogs.openDialog(createNewClassDialog(state, onConfirm));
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function openEditAssociationProfileDialogAction(
entity: SemanticModelRelationshipUsage,
) {
const state = createEditAssociationProfileDialogState(
classes, graph, visualModel, options.language, model, entity);
classes, graph, visualModel, options.language, model, entity.id);

const onConfirm = (nextState: EditAssociationProfileDialogState) => {
updateSemanticAssociationProfile(notifications, graph.models, entity, state, nextState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function openEditAttributeProfileDialogAction(
entity: SemanticModelRelationshipUsage,
) {
const state = createEditAttributeProfileDialogState(
classes, graph, visualModel, options.language, model, entity);
classes, graph, visualModel, options.language, model, entity.id);

const onConfirm = (nextState: EditAttributeProfileDialogState) => {
updateSemanticAttributeProfile(notifications, graph.models, entity, state, nextState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function openEditClassProfileDialogAction(
entity: SemanticModelClassUsage,
) {
const state = createEditClassProfileDialogState(
classes, graph, visualModel, options.language, model, entity);
classes, graph, visualModel, options.language, model, entity.id);

const onConfirm = (nextState: EditClassProfileDialogState) => {
updateSemanticClassProfile(notifications, entity, graph.models, state, nextState);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

import { VisualModel } from "@dataspecer/core-v2/visual-model";
import { InMemorySemanticModel } from "@dataspecer/core-v2/semantic-model/in-memory";
import { SemanticModelRelationshipUsage, isSemanticModelClassUsage, isSemanticModelRelationshipUsage } from "@dataspecer/core-v2/semantic-model/usage/concepts";
import { isSemanticModelClassUsage, isSemanticModelRelationshipUsage } from "@dataspecer/core-v2/semantic-model/usage/concepts";

import { ClassesContextType } from "../../context/classes-context";
import { ModelGraphContextType } from "../../context/model-context";
import { EditAssociationProfileDialogState } from "./edit-association-profile-dialog-controller";
import { getDomainAndRange } from "../../util/relationship-utils";
import { InvalidAggregation, MissingRelationshipEnds } from "../../application/error";
import { InvalidAggregation, MissingEntity, MissingRelationshipEnds } from "../../application/error";
import { createEntityProfileStateForEdit } from "../utilities/entity-profile-utilities";
import { createRelationshipProfileStateForEdit } from "../utilities/relationship-profile-utilities";
import { entityModelsMapToCmeVocabulary } from "../../dataspecer/semantic-model/semantic-model-adapter";
Expand All @@ -21,11 +21,16 @@ export function createEditAssociationProfileDialogState(
visualModel: VisualModel | null,
language: string,
model: InMemorySemanticModel,
entity: SemanticModelRelationshipUsage,
entityIdentifier: string,
): EditAssociationProfileDialogState {
const entities = graphContext.aggregatorView.getEntities();
const aggregated = entities[entity.id]?.aggregatedEntity;
if (!isSemanticModelRelationshipUsage(aggregated)) {
const aggregate = entities[entityIdentifier];
const entity = aggregate.rawEntity;
const aggregated = aggregate.aggregatedEntity;
if (entity === null) {
throw new MissingEntity(entityIdentifier);
}
if (!isSemanticModelRelationshipUsage(entity) || !isSemanticModelRelationshipUsage(aggregated)) {
throw new InvalidAggregation(entity, null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ function createForAssociation(

const owlThing = representOwlThing();

const availableDomains = [owlThing, ...classProfiles];
const availableRanges = [owlThing, ...classProfiles];
const relationshipProfileState = createRelationshipProfileStateForNew(
domain.concept, owlThing, domain.cardinality, classProfiles,
range.concept, owlThing, range.cardinality, classProfiles);
domain.concept, owlThing, domain.cardinality, availableDomains,
range.concept, owlThing, range.cardinality, availableRanges);

return {
...entityProfileState,
Expand Down Expand Up @@ -134,9 +136,11 @@ function createForAssociationProfile(

const owlThing = representOwlThing();

const availableDomains = [owlThing, ...classProfiles];
const availableRanges = [owlThing, ...classProfiles];
const relationshipProfileState = createRelationshipProfileStateForNew(
domain.concept, owlThing, domain.cardinality, classProfiles,
range.concept, owlThing, range.cardinality, classProfiles);
domain.concept, owlThing, domain.cardinality, availableDomains,
range.concept, owlThing, range.cardinality, availableRanges);

return {
...entityProfileState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function createCreateAssociationDialogState(
graphContext: ModelGraphContextType,
visualModel: VisualModel | null,
language: string,
defaultModelIdentifier: string | null,
): EditAssociationDialogState {

const models = [...graphContext.models.values()];
Expand All @@ -24,7 +25,8 @@ export function createCreateAssociationDialogState(

// EntityState

const entityState = createEntityStateForNew(language, vocabularies, configuration().nameToIri);
const entityState = createEntityStateForNew(
language, defaultModelIdentifier, vocabularies, configuration().nameToIri);

// SpecializationState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ function createForAttribute(
const undefinedDataType = representUndefinedDataType();
const dataTypes = [undefinedDataType, ...representDataTypes()];

const availableDomains = [owlThing, ...classProfiles];
const relationshipProfileState = createRelationshipProfileStateForNew(
domain.concept, owlThing, domain.cardinality, classProfiles,
domain.concept, owlThing, domain.cardinality, availableDomains,
range.concept, undefinedDataType, range.cardinality, dataTypes);

return {
Expand Down Expand Up @@ -152,8 +153,9 @@ function createForAttributeProfile(
const undefinedDataType = representUndefinedDataType();
const dataTypes = [undefinedDataType, ...representDataTypes()];

const availableDomains = [owlThing, ...classProfiles];
const relationshipProfileState = createRelationshipProfileStateForNew(
domain.concept, owlThing, domain.cardinality, classProfiles,
domain.concept, owlThing, domain.cardinality, availableDomains,
range.concept, undefinedDataType, range.cardinality, dataTypes);

return {
Expand Down
Loading

0 comments on commit b3ed4db

Please sign in to comment.