From 0e7eda14a8673589fe0d73574357f81ed033b79d Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sat, 14 Oct 2023 14:20:35 +0200 Subject: [PATCH 01/30] Save work --- .../iris/settings/IrisChatSubSettings.java | 44 ++ .../settings/IrisCodeEditorSubSettings.java | 83 ++++ .../iris/settings/IrisCourseSettings.java | 71 +++ .../iris/settings/IrisExerciseSettings.java | 57 +++ .../iris/settings/IrisGlobalSettings.java | 107 +++++ .../iris/settings/IrisHestiaSubSettings.java | 31 ++ .../iris/settings/IrisModelListConverter.java | 30 ++ .../domain/iris/settings/IrisSettings.java | 46 +- .../iris/settings/IrisSettingsType.java | 5 + .../domain/iris/settings/IrisSubSettings.java | 30 +- .../iris/settings/IrisSubSettingsType.java | 5 + .../iris/IrisSettingsRepository.java | 48 +- .../iris/IrisCombinedChatSubSettingsDTO.java | 58 +++ .../IrisCombinedCodeEditorSubSettingsDTO.java | 88 ++++ .../IrisCombinedHestiaSubSettingsDTO.java | 48 ++ .../dto/iris/IrisCombinedSettingsDTO.java | 4 + .../artemis/service/iris/IrisConstants.java | 26 +- .../service/iris/IrisSettingsService.java | 415 ------------------ .../iris/session/IrisChatSessionService.java | 9 +- .../session/IrisHestiaSessionService.java | 15 +- .../iris/settings/IrisSettingsService.java | 254 +++++++++++ .../iris/settings/IrisSubSettingsService.java | 201 +++++++++ .../admin/iris/AdminIrisSettingsResource.java | 2 +- .../web/rest/hestia/CodeHintResource.java | 2 +- .../web/rest/iris/IrisSessionResource.java | 2 +- .../web/rest/iris/IrisSettingsResource.java | 2 +- .../iris/AbstractIrisIntegrationTest.java | 2 +- 27 files changed, 1204 insertions(+), 481 deletions(-) create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisModelListConverter.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettingsType.java create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettingsType.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedCodeEditorSubSettingsDTO.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedHestiaSubSettingsDTO.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java delete mode 100644 src/main/java/de/tum/in/www1/artemis/service/iris/IrisSettingsService.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java create mode 100644 src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java new file mode 100644 index 000000000000..37215528f81b --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java @@ -0,0 +1,44 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.annotation.Nullable; +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +/** + * An IrisSubSettings object represents the settings for a specific feature of Iris. + * {@link IrisSettings} is the parent of this class. + */ +@Entity +@DiscriminatorValue("CHAT") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisChatSubSettings extends IrisSubSettings { + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate template; + + @Nullable + @Column(name = "rate_limit") + private Integer rateLimit; + + @Nullable + public IrisTemplate getTemplate() { + return template; + } + + public void setTemplate(@Nullable IrisTemplate template) { + this.template = template; + } + + @Nullable + public Integer getRateLimit() { + return rateLimit; + } + + public void setRateLimit(@Nullable Integer rateLimit) { + this.rateLimit = rateLimit; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java new file mode 100644 index 000000000000..3084cf517fc4 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java @@ -0,0 +1,83 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.annotation.Nullable; +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +/** + * An IrisSubSettings object represents the settings for a specific feature of Iris. + * {@link IrisSettings} is the parent of this class. + */ +@Entity +@DiscriminatorValue("CODE_EDITOR") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisCodeEditorSubSettings extends IrisSubSettings { + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate chatTemplate; + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate problemStatementGenerationTemplate; + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate templateRepoGenerationTemplate; + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate solutionRepoGenerationTemplate; + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate testRepoGenerationTemplate; + + @Nullable + public IrisTemplate getChatTemplate() { + return chatTemplate; + } + + public void setChatTemplate(@Nullable IrisTemplate chatTemplate) { + this.chatTemplate = chatTemplate; + } + + @Nullable + public IrisTemplate getProblemStatementGenerationTemplate() { + return problemStatementGenerationTemplate; + } + + public void setProblemStatementGenerationTemplate(@Nullable IrisTemplate problemStatementGenerationTemplate) { + this.problemStatementGenerationTemplate = problemStatementGenerationTemplate; + } + + @Nullable + public IrisTemplate getTemplateRepoGenerationTemplate() { + return templateRepoGenerationTemplate; + } + + public void setTemplateRepoGenerationTemplate(@Nullable IrisTemplate templateRepoGenerationTemplate) { + this.templateRepoGenerationTemplate = templateRepoGenerationTemplate; + } + + @Nullable + public IrisTemplate getSolutionRepoGenerationTemplate() { + return solutionRepoGenerationTemplate; + } + + public void setSolutionRepoGenerationTemplate(@Nullable IrisTemplate solutionRepoGenerationTemplate) { + this.solutionRepoGenerationTemplate = solutionRepoGenerationTemplate; + } + + @Nullable + public IrisTemplate getTestRepoGenerationTemplate() { + return testRepoGenerationTemplate; + } + + public void setTestRepoGenerationTemplate(@Nullable IrisTemplate testRepoGenerationTemplate) { + this.testRepoGenerationTemplate = testRepoGenerationTemplate; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java new file mode 100644 index 000000000000..d83cc00d4e02 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java @@ -0,0 +1,71 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.Course; + +/** + * An IrisSettings object represents the settings for Iris for a part of Artemis. + * These settings can be either global, course or exercise specific. + * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + */ +@Entity +@DiscriminatorValue("COURSE") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisCourseSettings extends IrisSettings { + + @OneToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "course_id", unique = true, nullable = false) + private Course course; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + @JoinColumn(name = "iris_chat_settings_id") + private IrisChatSubSettings irisChatSettings; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + @JoinColumn(name = "iris_hestia_settings_id") + private IrisHestiaSubSettings irisHestiaSettings; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "iris_code_editor_settings_id") + private IrisCodeEditorSubSettings irisCodeEditorSettings; + + @Override + public boolean isValid() { + return course != null; + } + + public Course getCourse() { + return course; + } + + public void setCourse(Course course) { + this.course = course; + } + + public IrisChatSubSettings getIrisChatSettings() { + return irisChatSettings; + } + + public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { + this.irisChatSettings = irisChatSettings; + } + + public IrisHestiaSubSettings getIrisHestiaSettings() { + return irisHestiaSettings; + } + + public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { + this.irisHestiaSettings = irisHestiaSettings; + } + + public IrisCodeEditorSubSettings getIrisCodeEditorSettings() { + return irisCodeEditorSettings; + } + + public void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings) { + this.irisCodeEditorSettings = irisCodeEditorSettings; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java new file mode 100644 index 000000000000..4b3df9985fe9 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java @@ -0,0 +1,57 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.Exercise; + +/** + * An IrisSettings object represents the settings for Iris for a part of Artemis. + * These settings can be either global, course or exercise specific. + * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + */ +@Entity +@DiscriminatorValue("EXERCISE") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisExerciseSettings extends IrisSettings { + + @OneToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "exercise_id", unique = true, nullable = false) + private Exercise exercise; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + @JoinColumn(name = "iris_chat_settings_id") + private IrisChatSubSettings irisChatSettings; + + @Override + public boolean isValid() { + return exercise != null; + } + + public Exercise getExercise() { + return exercise; + } + + public void setExercise(Exercise exercise) { + this.exercise = exercise; + } + + public IrisChatSubSettings getIrisChatSettings() { + return irisChatSettings; + } + + public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { + this.irisChatSettings = irisChatSettings; + } + + @Override + public IrisHestiaSubSettings getIrisHestiaSettings() { + return null; + } + + @Override + public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { + + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java new file mode 100644 index 000000000000..01691fe640a2 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java @@ -0,0 +1,107 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.persistence.*; + +import org.hibernate.Hibernate; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * An IrisSettings object represents the settings for Iris for a part of Artemis. + * These settings can be either global, course or exercise specific. + * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + */ +@Entity +@DiscriminatorValue("GLOBAL") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisGlobalSettings extends IrisSettings { + + @Column(name = "current_version") + private int currentVersion; + + @Column(name = "enable_auto_update_chat") + private boolean enableAutoUpdateChat; + + @Column(name = "enable_auto_update_hestia") + private boolean enableAutoUpdateHestia; + + @Column(name = "enable_auto_update_code_editor") + private boolean enableAutoUpdateCodeEditor; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "iris_chat_settings_id") + private IrisChatSubSettings irisChatSettings; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "iris_hestia_settings_id") + private IrisHestiaSubSettings irisHestiaSettings; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "iris_code_editor_settings_id") + private IrisCodeEditorSubSettings irisCodeEditorSettings; + + @Override + public boolean isValid() { + var chatSettingsValid = !Hibernate.isInitialized(irisChatSettings) || irisChatSettings == null + || (irisChatSettings.getTemplate() != null && irisChatSettings.getTemplate().getContent() != null && !irisChatSettings.getTemplate().getContent().isEmpty()); + var hestiaSettingsValid = !Hibernate.isInitialized(irisHestiaSettings) || irisHestiaSettings == null + || (irisHestiaSettings.getTemplate() != null && irisHestiaSettings.getTemplate().getContent() != null && !irisHestiaSettings.getTemplate().getContent().isEmpty()); + return currentVersion > 0 && chatSettingsValid && hestiaSettingsValid; + } + + public int getCurrentVersion() { + return currentVersion; + } + + public void setCurrentVersion(int currentVersion) { + this.currentVersion = currentVersion; + } + + public boolean isEnableAutoUpdateChat() { + return enableAutoUpdateChat; + } + + public void setEnableAutoUpdateChat(boolean enableAutoUpdateChat) { + this.enableAutoUpdateChat = enableAutoUpdateChat; + } + + public boolean isEnableAutoUpdateHestia() { + return enableAutoUpdateHestia; + } + + public void setEnableAutoUpdateHestia(boolean enableAutoUpdateHestia) { + this.enableAutoUpdateHestia = enableAutoUpdateHestia; + } + + public boolean isEnableAutoUpdateCodeEditor() { + return enableAutoUpdateCodeEditor; + } + + public void setEnableAutoUpdateCodeEditor(boolean enableAutoUpdateCodeEditor) { + this.enableAutoUpdateCodeEditor = enableAutoUpdateCodeEditor; + } + + public IrisChatSubSettings getIrisChatSettings() { + return irisChatSettings; + } + + public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { + this.irisChatSettings = irisChatSettings; + } + + public IrisHestiaSubSettings getIrisHestiaSettings() { + return irisHestiaSettings; + } + + public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { + this.irisHestiaSettings = irisHestiaSettings; + } + + public IrisCodeEditorSubSettings getIrisCodeEditorSettings() { + return irisCodeEditorSettings; + } + + public void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings) { + this.irisCodeEditorSettings = irisCodeEditorSettings; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java new file mode 100644 index 000000000000..66e5d0604a40 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java @@ -0,0 +1,31 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import javax.annotation.Nullable; +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +/** + * An IrisSubSettings object represents the settings for a specific feature of Iris. + * {@link IrisSettings} is the parent of this class. + */ +@Entity +@DiscriminatorValue("HESTIA") +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class IrisHestiaSubSettings extends IrisSubSettings { + + @Nullable + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private IrisTemplate template; + + @Nullable + public IrisTemplate getTemplate() { + return template; + } + + public void setTemplate(@Nullable IrisTemplate template) { + this.template = template; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisModelListConverter.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisModelListConverter.java new file mode 100644 index 000000000000..1bf7e6242cd6 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisModelListConverter.java @@ -0,0 +1,30 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +@Converter +public class IrisModelListConverter implements AttributeConverter, String> { + + @Override + public String convertToDatabaseColumn(Set type) { + if (type == null || type.isEmpty()) { + return null; + } + + return String.join(",", type); + } + + @Override + public Set convertToEntityAttribute(String value) { + var treeSet = new TreeSet(Comparator.naturalOrder()); + if (value != null) { + treeSet.addAll(Set.of(value.split(","))); + } + return treeSet; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java index cf540eacb07b..454c97bfbaa0 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java @@ -4,9 +4,10 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.ColumnDefault; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import de.tum.in.www1.artemis.domain.DomainObject; @@ -17,43 +18,22 @@ */ @Entity @Table(name = "iris_settings") +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ @JsonSubTypes.Type(value = IrisGlobalSettings.class, name = "global"), @JsonSubTypes.Type(value = IrisCourseSettings.class, name = "course"), + @JsonSubTypes.Type(value = IrisExerciseSettings.class, name = "exercise") }) @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class IrisSettings extends DomainObject { +public abstract class IrisSettings extends DomainObject { - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - @JoinColumn(name = "iris_chat_settings_id") - private IrisSubSettings irisChatSettings; + public abstract IrisChatSubSettings getIrisChatSettings(); - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - @JoinColumn(name = "iris_hestia_settings_id") - private IrisSubSettings irisHestiaSettings; + public abstract void setIrisChatSettings(IrisChatSubSettings irisChatSettings); - @Column(name = "is_global") - @ColumnDefault("false") - private boolean isGlobal = false; + public abstract IrisHestiaSubSettings getIrisHestiaSettings(); - public IrisSubSettings getIrisChatSettings() { - return irisChatSettings; - } + public abstract void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings); - public void setIrisChatSettings(IrisSubSettings irisChatSettings) { - this.irisChatSettings = irisChatSettings; - } - - public IrisSubSettings getIrisHestiaSettings() { - return irisHestiaSettings; - } - - public void setIrisHestiaSettings(IrisSubSettings irisHestiaSettings) { - this.irisHestiaSettings = irisHestiaSettings; - } - - public boolean isGlobal() { - return isGlobal; - } - - public void setGlobal(boolean global) { - isGlobal = global; - } + public abstract boolean isValid(); } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettingsType.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettingsType.java new file mode 100644 index 000000000000..90ec73a0bf87 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettingsType.java @@ -0,0 +1,5 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +public enum IrisSettingsType { + GLOBAL, COURSE, EXERCISE +} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java index 1d603967c8e3..3404404f24e0 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java @@ -1,5 +1,8 @@ package de.tum.in.www1.artemis.domain.iris.settings; +import java.util.HashSet; +import java.util.Set; + import javax.annotation.Nullable; import javax.persistence.*; @@ -7,9 +10,10 @@ import org.hibernate.annotations.CacheConcurrencyStrategy; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import de.tum.in.www1.artemis.domain.DomainObject; -import de.tum.in.www1.artemis.domain.iris.IrisTemplate; /** * An IrisSubSettings object represents the settings for a specific feature of Iris. @@ -17,19 +21,24 @@ */ @Entity @Table(name = "iris_sub_settings") +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ @JsonSubTypes.Type(value = IrisChatSubSettings.class, name = "chat"), @JsonSubTypes.Type(value = IrisHestiaSubSettings.class, name = "hestia"), + @JsonSubTypes.Type(value = IrisCodeEditorSubSettings.class, name = "code-editor") }) @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class IrisSubSettings extends DomainObject { +public abstract class IrisSubSettings extends DomainObject { @Column(name = "enabled") private boolean enabled = false; - @Nullable - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private IrisTemplate template; + @Column(name = "allowed_models") + @Convert(converter = IrisModelListConverter.class) + private Set allowedModels = new HashSet<>(); @Nullable - @Column(name = "preferredModel") + @Column(name = "preferred_model") private String preferredModel; public boolean isEnabled() { @@ -40,13 +49,12 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - @Nullable - public IrisTemplate getTemplate() { - return template; + public Set getAllowedModels() { + return allowedModels; } - public void setTemplate(@Nullable IrisTemplate template) { - this.template = template; + public void setAllowedModels(Set allowedModels) { + this.allowedModels = allowedModels; } @Nullable diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettingsType.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettingsType.java new file mode 100644 index 000000000000..9a33ab0867d0 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettingsType.java @@ -0,0 +1,5 @@ +package de.tum.in.www1.artemis.domain.iris.settings; + +public enum IrisSubSettingsType { + CHAT, HESTIA, CODE_EDITOR +} diff --git a/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java index 99dccb456c54..75490db7d5ba 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java @@ -1,11 +1,13 @@ package de.tum.in.www1.artemis.repository.iris; +import java.util.Optional; import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; +import de.tum.in.www1.artemis.domain.iris.settings.*; +import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException; /** * Spring Data repository for the IrisSettings entity. @@ -16,7 +18,47 @@ public interface IrisSettingsRepository extends JpaRepository findAllGlobalSettings(); + Set findAllGlobalSettings(); + + @Query(""" + SELECT irisSettings + FROM IrisSettings irisSettings + LEFT JOIN FETCH irisSettings.irisChatSettings ics + LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs + WHERE type(irisSettings) = IrisGlobalSettings + ORDER BY irisSettings.id DESC + LIMIT 1 + """) + Optional findGlobalSettings(); + + default IrisGlobalSettings findGlobalSettingsElseThrow() { + return findGlobalSettings().orElseThrow(() -> new EntityNotFoundException("Iris Global Settings")); + } + + @Query(""" + SELECT irisSettings + FROM IrisSettings irisSettings + LEFT JOIN FETCH irisSettings.irisChatSettings ics + LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs + WHERE type(irisSettings) = IrisCourseSettings + AND irisSettings.course.id = :courseId + """) + Optional findCourseSettings(Long courseId); + + @Query(""" + SELECT irisSettings + FROM IrisSettings irisSettings + LEFT JOIN FETCH irisSettings.irisChatSettings ics + LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs + WHERE type(irisSettings) = IrisExerciseSettings + AND irisSettings.exercise.id = :exerciseId + """) + Optional findExerciseSettings(Long exerciseId); + + default IrisSettings findByIdElseThrow(long existingSettingsId) { + return findById(existingSettingsId).orElseThrow(() -> new EntityNotFoundException("Iris Settings", existingSettingsId)); + } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java new file mode 100644 index 000000000000..79781ca4653b --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java @@ -0,0 +1,58 @@ +package de.tum.in.www1.artemis.service.dto.iris; + +import java.util.Set; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +public class IrisCombinedChatSubSettingsDTO { + + private boolean enabled; + + private Integer rateLimit; + + private Set allowedModels; + + private String preferredModel; + + private IrisTemplate template; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Integer getRateLimit() { + return rateLimit; + } + + public void setRateLimit(Integer rateLimit) { + this.rateLimit = rateLimit; + } + + public Set getAllowedModels() { + return allowedModels; + } + + public void setAllowedModels(Set allowedModels) { + this.allowedModels = allowedModels; + } + + public String getPreferredModel() { + return preferredModel; + } + + public void setPreferredModel(String preferredModel) { + this.preferredModel = preferredModel; + } + + public IrisTemplate getTemplate() { + return template; + } + + public void setTemplate(IrisTemplate template) { + this.template = template; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedCodeEditorSubSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedCodeEditorSubSettingsDTO.java new file mode 100644 index 000000000000..951612105414 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedCodeEditorSubSettingsDTO.java @@ -0,0 +1,88 @@ +package de.tum.in.www1.artemis.service.dto.iris; + +import java.util.Set; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +public class IrisCombinedCodeEditorSubSettingsDTO { + + private boolean enabled; + + private Set allowedModels; + + private String preferredModel; + + private IrisTemplate chatTemplate; + + private IrisTemplate problemStatementGenerationTemplate; + + private IrisTemplate templateRepoGenerationTemplate; + + private IrisTemplate solutionRepoGenerationTemplate; + + private IrisTemplate testRepoGenerationTemplate; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Set getAllowedModels() { + return allowedModels; + } + + public void setAllowedModels(Set allowedModels) { + this.allowedModels = allowedModels; + } + + public String getPreferredModel() { + return preferredModel; + } + + public void setPreferredModel(String preferredModel) { + this.preferredModel = preferredModel; + } + + public IrisTemplate getChatTemplate() { + return chatTemplate; + } + + public void setChatTemplate(IrisTemplate chatTemplate) { + this.chatTemplate = chatTemplate; + } + + public IrisTemplate getProblemStatementGenerationTemplate() { + return problemStatementGenerationTemplate; + } + + public void setProblemStatementGenerationTemplate(IrisTemplate problemStatementGenerationTemplate) { + this.problemStatementGenerationTemplate = problemStatementGenerationTemplate; + } + + public IrisTemplate getTemplateRepoGenerationTemplate() { + return templateRepoGenerationTemplate; + } + + public void setTemplateRepoGenerationTemplate(IrisTemplate templateRepoGenerationTemplate) { + this.templateRepoGenerationTemplate = templateRepoGenerationTemplate; + } + + public IrisTemplate getSolutionRepoGenerationTemplate() { + return solutionRepoGenerationTemplate; + } + + public void setSolutionRepoGenerationTemplate(IrisTemplate solutionRepoGenerationTemplate) { + this.solutionRepoGenerationTemplate = solutionRepoGenerationTemplate; + } + + public IrisTemplate getTestRepoGenerationTemplate() { + return testRepoGenerationTemplate; + } + + public void setTestRepoGenerationTemplate(IrisTemplate testRepoGenerationTemplate) { + this.testRepoGenerationTemplate = testRepoGenerationTemplate; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedHestiaSubSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedHestiaSubSettingsDTO.java new file mode 100644 index 000000000000..5a72e542eb63 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedHestiaSubSettingsDTO.java @@ -0,0 +1,48 @@ +package de.tum.in.www1.artemis.service.dto.iris; + +import java.util.Set; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; + +public class IrisCombinedHestiaSubSettingsDTO { + + private boolean enabled; + + private Set allowedModels; + + private String preferredModel; + + private IrisTemplate template; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Set getAllowedModels() { + return allowedModels; + } + + public void setAllowedModels(Set allowedModels) { + this.allowedModels = allowedModels; + } + + public String getPreferredModel() { + return preferredModel; + } + + public void setPreferredModel(String preferredModel) { + this.preferredModel = preferredModel; + } + + public IrisTemplate getTemplate() { + return template; + } + + public void setTemplate(IrisTemplate template) { + this.template = template; + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java new file mode 100644 index 000000000000..b0660455d6c1 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java @@ -0,0 +1,4 @@ +package de.tum.in.www1.artemis.service.dto.iris; + +public record IrisCombinedSettingsDTO(IrisCombinedChatSubSettingsDTO irisChatSettings, IrisCombinedHestiaSubSettingsDTO irisHestiaSettings) { +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisConstants.java b/src/main/java/de/tum/in/www1/artemis/service/iris/IrisConstants.java index f5626032bb11..75e26ec45aba 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisConstants.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/IrisConstants.java @@ -5,9 +5,9 @@ */ public final class IrisConstants { - private IrisConstants() { - // Utility class for constants - } + // The current version of the global settings defaults + // Increment this if you change the default settings + public static final int GLOBAL_SETTINGS_VERSION = 1; // The default guidance template for the chat feature public static final String DEFAULT_CHAT_TEMPLATE = """ @@ -112,4 +112,24 @@ private IrisConstants() { public static final String DEFAULT_HESTIA_TEMPLATE = """ TODO: Will be added in a future PR """; + + // The default guidance templates for the code editor feature + public static final String DEFAULT_CODE_EDITOR_CHAT_TEMPLATE = """ + """; + + public static final String DEFAULT_CODE_EDITOR_PROBLEM_STATEMENT_GENERATION_TEMPLATE = """ + """; + + public static final String DEFAULT_CODE_EDITOR_TEMPLATE_REPO_GENERATION_TEMPLATE = """ + """; + + public static final String DEFAULT_CODE_EDITOR_SOLUTION_REPO_GENERATION_TEMPLATE = """ + """; + + public static final String DEFAULT_CODE_EDITOR_TEST_REPO_GENERATION_TEMPLATE = """ + """; + + private IrisConstants() { + // Utility class for constants + } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/IrisSettingsService.java deleted file mode 100644 index 76a64e6d68be..000000000000 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisSettingsService.java +++ /dev/null @@ -1,415 +0,0 @@ -package de.tum.in.www1.artemis.service.iris; - -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import javax.ws.rs.ForbiddenException; - -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; -import org.springframework.core.env.Profiles; -import org.springframework.stereotype.Service; - -import de.tum.in.www1.artemis.domain.Course; -import de.tum.in.www1.artemis.domain.ProgrammingExercise; -import de.tum.in.www1.artemis.domain.iris.IrisTemplate; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettings; -import de.tum.in.www1.artemis.repository.CourseRepository; -import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; -import de.tum.in.www1.artemis.repository.iris.IrisSettingsRepository; -import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException; -import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; - -/** - * Service for managing {@link IrisSettings}. - * It is used to manage and combine global, course and exercise specific settings. - */ -@Service -public class IrisSettingsService { - - private final CourseRepository courseRepository; - - private final ApplicationContext applicationContext; - - private final IrisSettingsRepository irisSettingsRepository; - - private final ProgrammingExerciseRepository programmingExerciseRepository; - - public IrisSettingsService(CourseRepository courseRepository, ApplicationContext applicationContext, IrisSettingsRepository irisSettingsRepository, - ProgrammingExerciseRepository programmingExerciseRepository) { - this.courseRepository = courseRepository; - this.applicationContext = applicationContext; - this.irisSettingsRepository = irisSettingsRepository; - this.programmingExerciseRepository = programmingExerciseRepository; - } - - /** - * Hooks into the {@link ApplicationReadyEvent} and creates the global IrisSettings object on startup. - * - * @param event Specifies when this method gets called and provides the event with all application data - */ - @EventListener - public void execute(ApplicationReadyEvent event) throws Exception { - var settingsOptional = irisSettingsRepository.findAllGlobalSettings(); - if (settingsOptional.size() == 1) { - return; - } - else if (settingsOptional.size() > 1) { - var maxIdSettings = settingsOptional.stream().max(Comparator.comparingLong(IrisSettings::getId)).orElseThrow(); - settingsOptional.stream().filter(settings -> !Objects.equals(settings.getId(), maxIdSettings.getId())).forEach(irisSettingsRepository::delete); - return; - } - - if (event.getApplicationContext().getEnvironment().acceptsProfiles(Profiles.of("iris"))) { - var settings = createDefaultIrisSettings(true); - settings.setGlobal(true); - settings.getIrisChatSettings().setEnabled(true); - settings.getIrisChatSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_CHAT_TEMPLATE)); - settings.getIrisHestiaSettings().setEnabled(true); - settings.getIrisHestiaSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); - saveIrisSettings(settings); - } - } - - /** - * Check if the Iris Hestia feature is enabled for a programming exercise, else throw a {@link ForbiddenException}. - * See {@link #isIrisHestiaSessionEnabled(ProgrammingExercise)} - * - * @param programmingExercise the programming exercise for which to check the settings - */ - public void checkIsIrisHestiaSessionEnabledElseThrow(ProgrammingExercise programmingExercise) { - if (!isIrisHestiaSessionEnabled(programmingExercise)) { - throw new AccessForbiddenException("Iris Hestia feature is not enabled for programming exercise " + programmingExercise.getId()); - } - } - - /** - * Check if the Iris chat feature is enabled for a programming exercise, else throw a {@link ForbiddenException}. - * See {@link #isIrisChatSessionEnabled(ProgrammingExercise)} - * - * @param programmingExercise the programming exercise for which to check the settings - */ - public void checkIsIrisChatSessionEnabledElseThrow(ProgrammingExercise programmingExercise) { - if (!isIrisChatSessionEnabled(programmingExercise)) { - throw new AccessForbiddenException("Iris Chat feature is not enabled for programming exercise " + programmingExercise.getId()); - } - } - - /** - * Check if the Iris Hestia feature is enabled for a programming exercise. - * This is the case if they are enabled in the global and course settings and not disabled in the exercise settings. - * - * @param programmingExercise the programming exercise for which to check the settings - * @return true if the Iris Hestia feature is enabled, false otherwise - */ - public boolean isIrisHestiaSessionEnabled(ProgrammingExercise programmingExercise) { - if (programmingExercise == null || programmingExercise.getIrisSettings() == null) { - return false; - } - var settings = getCombinedIrisSettings(programmingExercise, true); - return settings.getIrisHestiaSettings().isEnabled(); - } - - /** - * Check if the Iris chat feature is enabled for a programming exercise. - * This is the case if they are enabled in the global, course, and exercise settings. - * - * @param programmingExercise the programming exercise for which to check the settings - * @return true if the Iris chat feature is enabled, false otherwise - */ - public boolean isIrisChatSessionEnabled(ProgrammingExercise programmingExercise) { - if (programmingExercise == null || programmingExercise.getIrisSettings() == null) { - return false; - } - var settings = getCombinedIrisSettings(programmingExercise, true); - return settings.getIrisChatSettings().isEnabled(); - } - - /** - * Get the global Iris settings. - * - * @return the global Iris settings - */ - public IrisSettings getGlobalSettings() { - return irisSettingsRepository.findAllGlobalSettings().stream().max(Comparator.comparingLong(IrisSettings::getId)).orElseThrow(); - } - - /** - * Get the Iris settings for a course. If no settings exist, a default settings object is created. - * {@link IrisSettingsService#addDefaultIrisSettingsTo(Course)} for more details about the default settings. - * - * @param course the course for which to get the settings - * @return the IrisSettings - */ - public IrisSettings getIrisSettingsOrDefault(Course course) { - if (course.getIrisSettings() == null || course.getIrisSettings().getId() == null) { - return createDefaultIrisSettings(true); - } - return irisSettingsRepository.findById(course.getIrisSettings().getId()).orElse(createDefaultIrisSettings(true)); - } - - /** - * Get the Iris settings for a programming exercise. If no settings exist, a default settings object is created. - * {@link IrisSettingsService#addDefaultIrisSettingsTo(Course)} for more details about the default settings. - * - * @param exercise the programming exercise for which to get the settings - * @return the IrisSettings - */ - public IrisSettings getIrisSettingsOrDefault(ProgrammingExercise exercise) { - if (exercise.getIrisSettings() == null || exercise.getIrisSettings().getId() == null) { - return createDefaultIrisSettings(false); - } - return irisSettingsRepository.findById(exercise.getIrisSettings().getId()).orElse(createDefaultIrisSettings(true)); - } - - /** - * Get the combined Iris settings for a course. Combines the global and course settings together. - * The course settings override the global settings, except for the enabled flag, which is combined. - * - * @param course the course for which to get the settings - * @param reduced if true only the enabled flag is combined, otherwise all settings are combined - * @return the combined IrisSettings - */ - public IrisSettings getCombinedIrisSettings(Course course, boolean reduced) { - var globalSettings = getGlobalSettings(); - var courseSettings = getIrisSettingsOrDefault(course); - - var combinedSettings = new IrisSettings(); - combinedSettings.setIrisChatSettings(combineSubSettings(globalSettings.getIrisChatSettings(), courseSettings.getIrisChatSettings(), false, reduced)); - combinedSettings.setIrisHestiaSettings(combineSubSettings(globalSettings.getIrisHestiaSettings(), courseSettings.getIrisHestiaSettings(), false, reduced)); - return combinedSettings; - } - - /** - * Get the combined Iris settings for a programming exercise. Combines the course and exercise settings together. - * The exercise settings override the course settings, but depending on the sub settings type, the combining strategy is different. - * ChatSettings: exercise settings are mandatory for the chat feature to be enabled - * HestiaSettings: exercise settings are optional for the hestia feature to be enabled - * - * @param programmingExercise the programming exercise for which to get the settings - * @param reduced if true only the enabled flag is combined, otherwise all settings are combined - * @return the combined IrisSettings - */ - public IrisSettings getCombinedIrisSettings(ProgrammingExercise programmingExercise, boolean reduced) { - var courseSettings = getCombinedIrisSettings(programmingExercise.getCourseViaExerciseGroupOrCourseMember(), reduced); - var exerciseSettings = getIrisSettingsOrDefault(programmingExercise); - - var combinedSettings = new IrisSettings(); - combinedSettings.setIrisChatSettings(combineSubSettings(courseSettings.getIrisChatSettings(), exerciseSettings.getIrisChatSettings(), false, reduced)); - combinedSettings.setIrisHestiaSettings(combineSubSettings(courseSettings.getIrisHestiaSettings(), exerciseSettings.getIrisHestiaSettings(), true, reduced)); - return combinedSettings; - } - - /** - * Combines the course and exercise sub-settings together. - * The exercise settings override the course settings, but depending on the exerciseSettingsAreOptional parameter, - * the combining strategy is different. If exerciseSettingsAreOptional is true, the course settings are used in full - * if the exercise settings are null. Otherwise the exercise settings have to be present. - * - * @param subSettings1 The course settings - * @param subSettings2 The exercise settings - * @param secondSubSettingsAreOptional Whether the exercise settings are optional or not - * @param reduced Whether only the enabled flag should be combined or all settings - * @return The combined sub-settings - */ - private IrisSubSettings combineSubSettings(IrisSubSettings subSettings1, IrisSubSettings subSettings2, boolean secondSubSettingsAreOptional, boolean reduced) { - if (secondSubSettingsAreOptional && subSettings2 == null) { - return subSettings1; - } - - var combinedSettings = new IrisSubSettings(); - - var enabled = subSettings2 != null && subSettings2.isEnabled() && subSettings1 != null && subSettings1.isEnabled() - && applicationContext.getEnvironment().acceptsProfiles(Profiles.of("iris")); - combinedSettings.setEnabled(enabled); - - if (!reduced) { - String preferredModel = null; - if (subSettings2 != null && subSettings2.getPreferredModel() != null) { - preferredModel = subSettings2.getPreferredModel(); - } - else if (subSettings1 != null && subSettings1.getPreferredModel() != null) { - preferredModel = subSettings1.getPreferredModel(); - } - combinedSettings.setPreferredModel(preferredModel); - - IrisTemplate template; - if (subSettings2 != null && subSettings2.getTemplate() != null) { - template = subSettings2.getTemplate(); - } - else if (subSettings1 != null) { - template = subSettings1.getTemplate(); - } - else { - template = null; - } - combinedSettings.setTemplate(template); - } - - return combinedSettings; - } - - /** - * Adds the default Iris settings to a course if they are not present yet. - * - * @param course The course to add the default Iris settings to - * @return The course with the default Iris settings - */ - public Course addDefaultIrisSettingsTo(Course course) { - if (course.getIrisSettings() != null) { - return course; - } - course.setIrisSettings(createDefaultIrisSettings(true)); - return courseRepository.save(course); - } - - /** - * Adds the default Iris settings to a programming exercise if they are not present yet. - * - * @param programmingExercise The programming exercise to add the default Iris settings to - * @return The programming exercise with the default Iris settings - */ - public ProgrammingExercise addDefaultIrisSettingsTo(ProgrammingExercise programmingExercise) { - if (programmingExercise.getIrisSettings() != null) { - return programmingExercise; - } - programmingExercise.setIrisSettings(createDefaultIrisSettings(false)); - return programmingExerciseRepository.save(programmingExercise); - } - - private IrisSettings createDefaultIrisSettings(boolean withOptionalSettings) { - var irisSettings = new IrisSettings(); - irisSettings.setIrisChatSettings(createDefaultIrisSubSettings()); - irisSettings.setIrisHestiaSettings(withOptionalSettings ? createDefaultIrisSubSettings() : null); - return irisSettings; - } - - private IrisSubSettings createDefaultIrisSubSettings() { - var subSettings = new IrisSubSettings(); - subSettings.setEnabled(false); - subSettings.setPreferredModel(null); - subSettings.setTemplate(null); - return subSettings; - } - - /** - * Save the Iris settings. Should always be used over directly calling the repository. - * Ensures that there is only one global Iris settings object. - * - * @param settings The Iris settings to save - * @return The saved Iris settings - */ - public IrisSettings saveIrisSettings(IrisSettings settings) { - if (settings.isGlobal()) { - var allGlobalSettings = irisSettingsRepository.findAllGlobalSettings(); - if (!allGlobalSettings.isEmpty() && !allGlobalSettings.stream().map(IrisSettings::getId).toList().equals(List.of(settings.getId()))) { - throw new IllegalStateException("There can only be one global Iris settings object."); - } - } - return irisSettingsRepository.save(settings); - } - - /** - * Save the global Iris settings. - * - * @param settings The Iris settings to save - * @return The saved Iris settings - */ - public IrisSettings saveGlobalIrisSettings(IrisSettings settings) { - if (!settings.isGlobal()) { - throw new BadRequestAlertException("The settings must be global", "IrisSettings", "notGlobal"); - } - var globalSettings = getGlobalSettings(); - globalSettings.setIrisChatSettings(copyIrisSubSettings(globalSettings.getIrisChatSettings(), settings.getIrisChatSettings())); - globalSettings.setIrisHestiaSettings(copyIrisSubSettings(globalSettings.getIrisHestiaSettings(), settings.getIrisHestiaSettings())); - return irisSettingsRepository.save(globalSettings); - } - - /** - * Save the Iris settings for a course. - * - * @param course The course for which to save the settings - * @param settings The Iris settings to save - * @return The saved Iris settings - */ - public IrisSettings saveIrisSettings(Course course, IrisSettings settings) { - var existingSettingsOptional = getIrisSettings(course); - if (existingSettingsOptional.isPresent()) { - var existingSettings = existingSettingsOptional.get(); - existingSettings.setIrisChatSettings(copyIrisSubSettings(existingSettings.getIrisChatSettings(), settings.getIrisChatSettings())); - existingSettings.setIrisHestiaSettings(copyIrisSubSettings(existingSettings.getIrisHestiaSettings(), settings.getIrisHestiaSettings())); - return saveIrisSettings(existingSettings); - } - else { - settings.setId(null); - course.setIrisSettings(saveIrisSettings(settings)); - var updatedCourse = courseRepository.save(course); - return updatedCourse.getIrisSettings(); - } - } - - /** - * Save the Iris settings for a programming exercise. - * - * @param exercise the programming exercise for which to save the settings - * @param settings the Iris settings to save - * @return the saved Iris settings - */ - public IrisSettings saveIrisSettings(ProgrammingExercise exercise, IrisSettings settings) { - var existingSettingsOptional = getIrisSettings(exercise); - if (existingSettingsOptional.isPresent()) { - var existingSettings = existingSettingsOptional.get(); - existingSettings.setIrisChatSettings(copyIrisSubSettings(existingSettings.getIrisChatSettings(), settings.getIrisChatSettings())); - existingSettings.setIrisHestiaSettings(copyIrisSubSettings(existingSettings.getIrisHestiaSettings(), settings.getIrisHestiaSettings())); - return saveIrisSettings(existingSettings); - } - else { - settings.setId(null); - exercise.setIrisSettings(saveIrisSettings(settings)); - var updatedExercise = programmingExerciseRepository.save(exercise); - return updatedExercise.getIrisSettings(); - } - } - - private IrisSubSettings copyIrisSubSettings(IrisSubSettings target, IrisSubSettings source) { - if (target == null || source == null) { - return source; - } - target.setEnabled(source.isEnabled()); - target.setPreferredModel(source.getPreferredModel()); - if (!Objects.equals(source.getTemplate(), target.getTemplate())) { - target.setTemplate(source.getTemplate()); - } - return target; - } - - /** - * Get the Iris settings for a course. If no settings exist, an empty optional is returned. - * - * @param course the course for which to get the settings - * @return the IrisSettings - */ - private Optional getIrisSettings(Course course) { - if (course.getIrisSettings() == null) { - return Optional.empty(); - } - return irisSettingsRepository.findById(course.getIrisSettings().getId()); - } - - /** - * Get the Iris settings for a course. If no settings exist, an empty optional is returned. - * - * @param exercise the course for which to get the settings - * @return the IrisSettings - */ - private Optional getIrisSettings(ProgrammingExercise exercise) { - if (exercise.getIrisSettings() == null) { - return Optional.empty(); - } - return irisSettingsRepository.findById(exercise.getIrisSettings().getId()); - } -} diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java index 508e8c5b7afa..ab64ad3d2b77 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisChatSessionService.java @@ -18,6 +18,7 @@ import de.tum.in.www1.artemis.domain.iris.IrisMessageSender; import de.tum.in.www1.artemis.domain.iris.session.IrisChatSession; import de.tum.in.www1.artemis.domain.iris.session.IrisSession; +import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettingsType; import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository; import de.tum.in.www1.artemis.repository.ProgrammingSubmissionRepository; import de.tum.in.www1.artemis.repository.TemplateProgrammingExerciseParticipationRepository; @@ -28,9 +29,9 @@ import de.tum.in.www1.artemis.service.connectors.GitService; import de.tum.in.www1.artemis.service.connectors.iris.IrisConnectorService; import de.tum.in.www1.artemis.service.iris.IrisMessageService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; import de.tum.in.www1.artemis.service.iris.IrisWebsocketService; import de.tum.in.www1.artemis.service.iris.exception.IrisNoResponseException; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException; import de.tum.in.www1.artemis.web.rest.errors.InternalServerErrorException; @@ -107,7 +108,7 @@ public void checkHasAccessToIrisSession(IrisSession session, User user) { @Override public void checkIsIrisActivated(IrisSession session) { var chatSession = castToSessionType(session, IrisChatSession.class); - irisSettingsService.checkIsIrisChatSessionEnabledElseThrow(chatSession.getExercise()); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.CHAT, chatSession.getExercise()); } /** @@ -145,8 +146,8 @@ public void requestAndHandleResponse(IrisSession session) { parameters.put("session", fullSession); addDiffAndTemplatesForStudentAndExerciseIfPossible(chatSession.getUser(), exercise, parameters); - var irisSettings = irisSettingsService.getCombinedIrisSettings(exercise, false); - irisConnectorService.sendRequest(irisSettings.getIrisChatSettings().getTemplate(), irisSettings.getIrisChatSettings().getPreferredModel(), parameters) + var irisSettings = irisSettingsService.getCombinedIrisSettingsFor(exercise, false); + irisConnectorService.sendRequest(irisSettings.irisChatSettings().getTemplate(), irisSettings.irisChatSettings().getPreferredModel(), parameters) .handleAsync((irisMessage, throwable) -> { if (throwable != null) { log.error("Error while getting response from Iris model", throwable); diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisHestiaSessionService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisHestiaSessionService.java index 5aeea216407b..9e70973d2ef1 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisHestiaSessionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/session/IrisHestiaSessionService.java @@ -19,12 +19,13 @@ import de.tum.in.www1.artemis.domain.iris.*; import de.tum.in.www1.artemis.domain.iris.session.IrisHestiaSession; import de.tum.in.www1.artemis.domain.iris.session.IrisSession; +import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettingsType; import de.tum.in.www1.artemis.repository.iris.IrisSessionRepository; import de.tum.in.www1.artemis.security.Role; import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.connectors.iris.IrisConnectorService; import de.tum.in.www1.artemis.service.iris.IrisMessageService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.web.rest.errors.InternalServerErrorException; /** @@ -87,14 +88,14 @@ public CodeHint generateDescription(CodeHint codeHint) { irisMessageService.saveMessage(userMessage, irisSession, IrisMessageSender.USER); irisSession = (IrisHestiaSession) irisSessionRepository.findByIdWithMessagesAndContents(irisSession.getId()); Map parameters = Map.of("codeHint", irisSession.getCodeHint()); - var irisSettings = irisSettingsService.getCombinedIrisSettings(irisSession.getCodeHint().getExercise(), false); + var irisSettings = irisSettingsService.getCombinedIrisSettingsFor(irisSession.getCodeHint().getExercise(), false); try { - var irisMessage1 = irisConnectorService - .sendRequest(irisSettings.getIrisHestiaSettings().getTemplate(), irisSettings.getIrisHestiaSettings().getPreferredModel(), parameters).get(); + var irisMessage1 = irisConnectorService.sendRequest(irisSettings.irisHestiaSettings().getTemplate(), irisSettings.irisHestiaSettings().getPreferredModel(), parameters) + .get(); irisMessageService.saveMessage(irisMessage1.message(), irisSession, IrisMessageSender.LLM); irisSession = (IrisHestiaSession) irisSessionRepository.findByIdWithMessagesAndContents(irisSession.getId()); - var irisMessage2 = irisConnectorService - .sendRequest(irisSettings.getIrisHestiaSettings().getTemplate(), irisSettings.getIrisHestiaSettings().getPreferredModel(), parameters).get(); + var irisMessage2 = irisConnectorService.sendRequest(irisSettings.irisHestiaSettings().getTemplate(), irisSettings.irisHestiaSettings().getPreferredModel(), parameters) + .get(); irisMessageService.saveMessage(irisMessage2.message(), irisSession, IrisMessageSender.LLM); codeHint.setContent(irisMessage1.message().getContent().stream().map(IrisMessageContent::getTextContent).collect(Collectors.joining("\n"))); @@ -179,6 +180,6 @@ public void checkHasAccessToIrisSession(IrisSession irisSession, User user) { @Override public void checkIsIrisActivated(IrisSession session) { var irisHestiaSession = castToSessionType(session, IrisHestiaSession.class); - irisSettingsService.checkIsIrisHestiaSessionEnabledElseThrow(irisHestiaSession.getCodeHint().getExercise()); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.HESTIA, irisHestiaSession.getCodeHint().getExercise()); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java new file mode 100644 index 000000000000..7fd00a47401f --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -0,0 +1,254 @@ +package de.tum.in.www1.artemis.service.iris.settings; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Objects; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import de.tum.in.www1.artemis.domain.Course; +import de.tum.in.www1.artemis.domain.Exercise; +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; +import de.tum.in.www1.artemis.domain.iris.settings.*; +import de.tum.in.www1.artemis.repository.iris.IrisSettingsRepository; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedSettingsDTO; +import de.tum.in.www1.artemis.service.iris.IrisConstants; +import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenAlertException; +import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; +import de.tum.in.www1.artemis.web.rest.errors.ConflictException; + +/** + * Service for managing {@link IrisSettings}. + * This service is responsible for CRUD operations on {@link IrisSettings}. + * It also provides methods for combining multiple {@link IrisSettings} and checking if a certain Iris feature is + * enabled for an exercise. + */ +@Service +@Profile("iris") +public class IrisSettingsService { + + private final IrisSettingsRepository irisSettingsRepository; + + private final IrisSubSettingsService irisSubSettingsService; + + public IrisSettingsService(IrisSettingsRepository irisSettingsRepository, IrisSubSettingsService irisSubSettingsService) { + this.irisSettingsRepository = irisSettingsRepository; + this.irisSubSettingsService = irisSubSettingsService; + } + + /** + * Hooks into the {@link ApplicationReadyEvent} and creates or updates the global IrisSettings object on startup. + * + * @param event Specifies when this method gets called and provides the event with all application data + */ + @EventListener + public void execute(ApplicationReadyEvent event) throws Exception { + var allGlobalSettings = irisSettingsRepository.findAllGlobalSettings(); + if (allGlobalSettings.isEmpty()) { + createInitialGlobalSettings(); + return; + } + if (allGlobalSettings.size() > 1) { + var maxIdSettings = allGlobalSettings.stream().max(Comparator.comparingLong(IrisSettings::getId)).orElseThrow(); + allGlobalSettings.stream().filter(settings -> !Objects.equals(settings.getId(), maxIdSettings.getId())).forEach(irisSettingsRepository::delete); + autoUpdateGlobalSettings(maxIdSettings); + } + else { + autoUpdateGlobalSettings(allGlobalSettings.stream().findFirst().get()); + } + } + + /** + * Creates the initial global IrisSettings object. + */ + private void createInitialGlobalSettings() { + var settings = new IrisGlobalSettings(); + settings.setCurrentVersion(IrisConstants.GLOBAL_SETTINGS_VERSION); + + var chatSettings = new IrisChatSubSettings(); + chatSettings.setEnabled(false); + chatSettings.setTemplate(new IrisTemplate(IrisConstants.DEFAULT_CHAT_TEMPLATE)); + settings.setIrisChatSettings(chatSettings); + + var hestiaSettings = new IrisHestiaSubSettings(); + hestiaSettings.setEnabled(false); + hestiaSettings.setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); + settings.setIrisHestiaSettings(hestiaSettings); + + updateIrisCourseEditorSettings(settings); + + saveIrisSettings(settings); + } + + /** + * Auto updates the global IrisSettings object if the current version is outdated. + * + * @param settings The global IrisSettings object to update + */ + private void autoUpdateGlobalSettings(IrisGlobalSettings settings) { + if (settings.getCurrentVersion() < IrisConstants.GLOBAL_SETTINGS_VERSION) { + if (settings.isEnableAutoUpdateChat()) { + settings.getIrisChatSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_CHAT_TEMPLATE)); + } + if (settings.isEnableAutoUpdateHestia()) { + settings.getIrisHestiaSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); + } + if (settings.isEnableAutoUpdateCodeEditor()) { + updateIrisCourseEditorSettings(settings); + } + settings.setCurrentVersion(IrisConstants.GLOBAL_SETTINGS_VERSION); + saveIrisSettings(settings); + } + } + + private static void updateIrisCourseEditorSettings(IrisGlobalSettings settings) { + var irisCodeEditorSettings = settings.getIrisCodeEditorSettings(); + if (irisCodeEditorSettings == null) { + irisCodeEditorSettings = new IrisCodeEditorSubSettings(); + irisCodeEditorSettings.setEnabled(false); + } + irisCodeEditorSettings.setChatTemplate(new IrisTemplate(IrisConstants.DEFAULT_CODE_EDITOR_CHAT_TEMPLATE)); + irisCodeEditorSettings.setProblemStatementGenerationTemplate(new IrisTemplate(IrisConstants.DEFAULT_CODE_EDITOR_PROBLEM_STATEMENT_GENERATION_TEMPLATE)); + irisCodeEditorSettings.setTemplateRepoGenerationTemplate(new IrisTemplate(IrisConstants.DEFAULT_CODE_EDITOR_TEMPLATE_REPO_GENERATION_TEMPLATE)); + irisCodeEditorSettings.setSolutionRepoGenerationTemplate(new IrisTemplate(IrisConstants.DEFAULT_CODE_EDITOR_SOLUTION_REPO_GENERATION_TEMPLATE)); + irisCodeEditorSettings.setTestRepoGenerationTemplate(new IrisTemplate(IrisConstants.DEFAULT_CODE_EDITOR_TEST_REPO_GENERATION_TEMPLATE)); + settings.setIrisCodeEditorSettings(irisCodeEditorSettings); + } + + public IrisGlobalSettings getGlobalSettings() { + return irisSettingsRepository.findGlobalSettingsElseThrow(); + } + + /** + * Save the Iris settings. Should always be used over directly calling the repository. + * Automatically decides whether to save a new Iris settings object or update an existing one. + * + * @param settings The Iris settings to save + * @return The saved Iris settings + */ + public T saveIrisSettings(T settings) { + if (settings.getId() == null) { + return saveNewIrisSettings(settings); + } + else { + return updateIrisSettings(settings.getId(), settings); + } + } + + /** + * Save the Iris settings. Should always be used over directly calling the repository. + * Ensures that there is only one global Iris settings object. + * + * @param settings The Iris settings to save + * @return The saved Iris settings + */ + public T saveNewIrisSettings(T settings) { + if (settings.getId() != null) { + throw new BadRequestAlertException("New Iris settings cannot already have an ID", "IrisSettings", "notNew"); + } + if (settings instanceof IrisGlobalSettings) { + throw new BadRequestAlertException("You can not create new global settings", "IrisSettings", "notGlobal"); + } + if (!settings.isValid()) { + throw new BadRequestAlertException("New Iris settings are not valid", "IrisSettings", "notValid"); + } + return irisSettingsRepository.save(settings); + } + + @SuppressWarnings("unchecked") + public T updateIrisSettings(long existingSettingsId, T settingsUpdate) { + if (!Objects.equals(existingSettingsId, settingsUpdate.getId())) { + throw new ConflictException("Existing Iris settings ID does not match update ID", "IrisSettings", "idMismatch"); + } + if (!settingsUpdate.isValid()) { + throw new BadRequestAlertException("Updated Iris settings are not valid", "IrisSettings", "notValid"); + } + + var existingSettings = irisSettingsRepository.findByIdElseThrow(existingSettingsId); + + if (existingSettings instanceof IrisGlobalSettings globalSettings && settingsUpdate instanceof IrisGlobalSettings globalSettingsUpdate) { + return (T) updateGlobalSettings(globalSettings, globalSettingsUpdate); + } + else if (existingSettings instanceof IrisCourseSettings courseSettings && settingsUpdate instanceof IrisCourseSettings courseSettingsUpdate) { + return (T) updateCourseSettings(courseSettings, courseSettingsUpdate); + } + else if (existingSettings instanceof IrisExerciseSettings exerciseSettings && settingsUpdate instanceof IrisExerciseSettings exerciseSettingsUpdate) { + return (T) updateExerciseSettings(exerciseSettings, exerciseSettingsUpdate); + } + else { + throw new BadRequestAlertException("Unknown Iris settings type", "IrisSettings", "unknownType"); + } + } + + private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSettings, IrisGlobalSettings settingsUpdate) { + existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null)); + existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null)); + return irisSettingsRepository.save(existingSettings); + } + + private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSettings, IrisCourseSettings settingsUpdate) { + var parentSettings = getGlobalSettings(); + return irisSettingsRepository.save(existingSettings); + } + + private IrisExerciseSettings updateExerciseSettings(IrisExerciseSettings existingSettings, IrisExerciseSettings settingsUpdate) { + + return irisSettingsRepository.save(existingSettings); + } + + /** + * Checks whether an Iris feature is enabled for an exercise. + * + * @param type The Iris feature to check + * @param exercise The exercise to check + * @return Whether the Iris feature is enabled for the exercise + */ + public boolean isEnabledFor(IrisSubSettingsType type, Exercise exercise) { + var settings = getCombinedIrisSettingsFor(exercise, true); + return switch (type) { + case CHAT -> settings.irisChatSettings().isEnabled(); + case HESTIA -> settings.irisHestiaSettings().isEnabled(); + case CODE_EDITOR -> false; // FIXME: Implement this in another PR + }; + } + + /** + * Checks whether an Iris feature is enabled for an exercise. + * Throws an exception if the feature is disabled. + * + * @param type The Iris feature to check + * @param exercise The exercise to check + */ + public void isEnabledForElseThrow(IrisSubSettingsType type, Exercise exercise) { + if (!isEnabledFor(type, exercise)) { + throw new AccessForbiddenAlertException("The Iris " + type.name() + " feature is disabled for this exercise.", "Iris", "iris.chatDisabled"); + } + } + + public IrisCombinedSettingsDTO getCombinedIrisGlobalSettings() { + var settingsList = new ArrayList(); + settingsList.add(getGlobalSettings()); + + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, false), irisSubSettingsService.combineHestiaSettings(settingsList, false)); + } + + public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Course course, boolean minimal) { + var settingsList = new ArrayList(); + settingsList.add(getGlobalSettings()); + irisSettingsRepository.findCourseSettings(course.getId()).ifPresent(settingsList::add); + + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal)); + } + + public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Exercise exercise, boolean minimal) { + var settingsList = new ArrayList(); + settingsList.add(getGlobalSettings()); + irisSettingsRepository.findCourseSettings(exercise.getCourseViaExerciseGroupOrCourseMember().getId()).ifPresent(settingsList::add); + irisSettingsRepository.findExerciseSettings(exercise.getId()).ifPresent(settingsList::add); + + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal)); + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java new file mode 100644 index 000000000000..92d65440f521 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -0,0 +1,201 @@ +package de.tum.in.www1.artemis.service.iris.settings; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Set; +import java.util.function.Function; + +import org.springframework.stereotype.Service; + +import de.tum.in.www1.artemis.domain.iris.IrisTemplate; +import de.tum.in.www1.artemis.domain.iris.settings.*; +import de.tum.in.www1.artemis.service.AuthorizationCheckService; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedChatSubSettingsDTO; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedCodeEditorSubSettingsDTO; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedHestiaSubSettingsDTO; + +/** + * Service for handling {@link IrisSettings} objects. + */ +@Service +public class IrisSubSettingsService { + + private final AuthorizationCheckService authCheckService; + + public IrisSubSettingsService(AuthorizationCheckService authCheckService) { + this.authCheckService = authCheckService; + } + + public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings) { + if (currentSettings == null) { + currentSettings = new IrisChatSubSettings(); + } + currentSettings.setEnabled(newSettings.isEnabled()); + currentSettings.setRateLimit(newSettings.getRateLimit()); + currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); + currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), + parentSettings != null ? parentSettings.getAllowedModels() : null)); + currentSettings.setTemplate(newSettings.getTemplate()); + return currentSettings; + } + + public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings) { + if (currentSettings == null) { + currentSettings = new IrisHestiaSubSettings(); + } + currentSettings.setEnabled(newSettings.isEnabled()); + currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); + currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), + parentSettings != null ? parentSettings.getAllowedModels() : null)); + currentSettings.setTemplate(newSettings.getTemplate()); + return currentSettings; + } + + public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings) { + if (currentSettings == null) { + currentSettings = new IrisCodeEditorSubSettings(); + } + currentSettings.setEnabled(newSettings.isEnabled()); + currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); + currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), + parentSettings != null ? parentSettings.getAllowedModels() : null)); + currentSettings.setChatTemplate(newSettings.getChatTemplate()); + currentSettings.setProblemStatementGenerationTemplate(newSettings.getProblemStatementGenerationTemplate()); + currentSettings.setTemplateRepoGenerationTemplate(newSettings.getTemplateRepoGenerationTemplate()); + currentSettings.setSolutionRepoGenerationTemplate(newSettings.getSolutionRepoGenerationTemplate()); + currentSettings.setTestRepoGenerationTemplate(newSettings.getTestRepoGenerationTemplate()); + return currentSettings; + } + + /** + * Filters the allowed models of a sub settings object. + * If the user is an admin, all models are allowed. + * Otherwise, only models that are allowed by the parent settings or the current settings are allowed. + * + * @param allowedModels The allowed models of the current settings. + * @param updatedAllowedModels The allowed models of the updated settings. + * @return The filtered allowed models. + */ + private Set selectAllowedModels(Set allowedModels, Set updatedAllowedModels) { + return authCheckService.isAdmin() ? updatedAllowedModels : allowedModels; + } + + /** + * Validates the preferred model of a sub settings object. + * If the user is an admin, all models are allowed. + * Otherwise, only models that are allowed by the current settings are allowed. + * + * @param preferredModel The preferred model of the current settings. + * @param newPreferredModel The preferred model of the updated settings. + * @param allowedModels The allowed models of the current settings. + * @param parentAllowedModels The allowed models of the parent settings. + * @return The validated preferred model. + */ + private String validatePreferredModel(String preferredModel, String newPreferredModel, Set allowedModels, Set parentAllowedModels) { + if (newPreferredModel == null || newPreferredModel.isBlank()) { + return null; + } + else if (authCheckService.isAdmin()) { + return newPreferredModel; + } + else if (allowedModels != null && allowedModels.contains(newPreferredModel)) { + return newPreferredModel; + } + else if (parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)) { + return newPreferredModel; + } + else { + return preferredModel; + } + } + + /** + * Combines the chat settings of multiple {@link IrisSettings} objects. + * If minimal is true, the returned object will only contain the enabled and rateLimit fields. + * The minimal version can safely be sent to students. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param minimal Whether to return a minimal version of the combined settings. + * @return Combined chat settings. + */ + public IrisCombinedChatSubSettingsDTO combineChatSettings(ArrayList settingsList, boolean minimal) { + var combinedChatSettings = new IrisCombinedChatSubSettingsDTO(); + combinedChatSettings.setEnabled(getCombinedEnabled(settingsList, IrisSettings::getIrisChatSettings)); + combinedChatSettings.setRateLimit(getCombinedRateLimit(settingsList)); + if (!minimal) { + combinedChatSettings.setPreferredModel(getCombinedPreferredModel(settingsList, IrisSettings::getIrisChatSettings)); + combinedChatSettings.setTemplate(getCombinedTemplate(settingsList, IrisSettings::getIrisChatSettings)); + } + return combinedChatSettings; + } + + /** + * Combines the Hestia settings of multiple {@link IrisSettings} objects. + * If minimal is true, the returned object will only contain the enabled field. + * The minimal version can safely be sent to students. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param minimal Whether to return a minimal version of the combined settings. + * @return Combined Hestia settings. + */ + public IrisCombinedHestiaSubSettingsDTO combineHestiaSettings(ArrayList settingsList, boolean minimal) { + var combinedHestiaSettings = new IrisCombinedHestiaSubSettingsDTO(); + combinedHestiaSettings.setEnabled(getCombinedEnabled(settingsList, IrisSettings::getIrisHestiaSettings)); + combinedHestiaSettings.setPreferredModel(getCombinedPreferredModel(settingsList, IrisSettings::getIrisHestiaSettings)); + if (!minimal) { + combinedHestiaSettings.setTemplate(getCombinedTemplate(settingsList, IrisSettings::getIrisHestiaSettings)); + } + return combinedHestiaSettings; + } + + /** + * Combines the enabled field of multiple {@link IrisSettings} objects. + * Simply &&s all enabled fields together. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. + * @return Combined enabled field. + */ + private boolean getCombinedEnabled(ArrayList settingsList, Function subSettingsFunction) { + return settingsList.stream().map(subSettingsFunction).allMatch(IrisSubSettings::isEnabled); + } + + /** + * Combines the rateLimit field of multiple {@link IrisSettings} objects. + * Simply takes the minimum rateLimit. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @return Combined rateLimit field. + */ + private Integer getCombinedRateLimit(ArrayList settingsList) { + return settingsList.stream().map(IrisSettings::getIrisChatSettings).map(IrisChatSubSettings::getRateLimit).filter(rateLimit -> rateLimit != null && rateLimit >= 0) + .min(Comparator.comparingInt(Integer::intValue)).orElse(null); + } + + /** + * Combines the preferredModel field of multiple {@link IrisSettings} objects. + * Simply takes the last preferredModel. + * TODO + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. + * @return Combined preferredModel field. + */ + private String getCombinedPreferredModel(ArrayList settingsList, Function subSettingsFunction) { + return settingsList.stream().map(subSettingsFunction).map(IrisSubSettings::getPreferredModel).filter(model -> model != null && !model.isBlank()) + .reduce((first, second) -> second).orElseThrow(); + } + + /** + * Combines the template field of multiple {@link IrisSettings} objects. + * Simply takes the last template. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. + * @return Combined template field. + */ + private IrisTemplate getCombinedTemplate(ArrayList settingsList, Function subSettingsFunction) { + return settingsList.stream().map(subSettingsFunction).map(IrisSubSettings::getTemplate).filter(template -> template != null && !template.getContent().isBlank()) + .reduce((first, second) -> second).orElseThrow(); + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java index 19048d1ef407..ce0f9e00eef4 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java @@ -8,7 +8,7 @@ import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; import de.tum.in.www1.artemis.security.annotations.EnforceAdmin; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; /** * REST controller for managing {@link IrisSettings}. diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java index d7c248195cfc..a1fec3e5fd55 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java @@ -18,7 +18,7 @@ import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastEditor; import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.hestia.CodeHintService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException; import de.tum.in.www1.artemis.web.rest.errors.ConflictException; diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java index 3d81309bd6da..0f4fdfd30e95 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java @@ -20,7 +20,7 @@ import de.tum.in.www1.artemis.service.connectors.iris.IrisHealthIndicator; import de.tum.in.www1.artemis.service.connectors.iris.dto.IrisStatusDTO; import de.tum.in.www1.artemis.service.iris.IrisSessionService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; /** * REST controller for managing {@link IrisSession}. diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java index f5692f388a3c..4ae2431904e6 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java @@ -10,7 +10,7 @@ import de.tum.in.www1.artemis.security.Role; import de.tum.in.www1.artemis.security.annotations.*; import de.tum.in.www1.artemis.service.AuthorizationCheckService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; /** * REST controller for managing {@link IrisSettings}. diff --git a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java index f2ccfa6cdfab..64474e9f82c2 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java @@ -24,8 +24,8 @@ import de.tum.in.www1.artemis.repository.CourseRepository; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; import de.tum.in.www1.artemis.repository.iris.IrisTemplateRepository; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; import de.tum.in.www1.artemis.service.iris.IrisWebsocketService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.user.UserUtilService; public abstract class AbstractIrisIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTest { From e7cc8dd67840b695c196e66f9ac7e19450f15804 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Mon, 16 Oct 2023 15:07:18 +0200 Subject: [PATCH 02/30] Save work --- .../iris/settings/IrisChatSubSettings.java | 13 ++++++++++ .../domain/iris/settings/IrisSubSettings.java | 26 ------------------- .../service/iris/IrisRateLimitService.java | 1 + 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java index 37215528f81b..ac94b548b586 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java @@ -24,6 +24,10 @@ public class IrisChatSubSettings extends IrisSubSettings { @Column(name = "rate_limit") private Integer rateLimit; + @Nullable + @Column(name = "rate_limit_timeframe_hours") + private Integer rateLimitTimeframeHours; + @Nullable public IrisTemplate getTemplate() { return template; @@ -41,4 +45,13 @@ public Integer getRateLimit() { public void setRateLimit(@Nullable Integer rateLimit) { this.rateLimit = rateLimit; } + + @Nullable + public Integer getRateLimitTimeframeHours() { + return rateLimitTimeframeHours; + } + + public void setRateLimitTimeframeHours(@Nullable Integer rateLimitTimeframeHours) { + this.rateLimitTimeframeHours = rateLimitTimeframeHours; + } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java index dfca8c242d17..3404404f24e0 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java @@ -41,14 +41,6 @@ public abstract class IrisSubSettings extends DomainObject { @Column(name = "preferred_model") private String preferredModel; - @Nullable - @Column(name = "rateLimit") - private Integer rateLimit; - - @Nullable - @Column(name = "rateLimitTimeframeHours") - private Integer rateLimitTimeframeHours; - public boolean isEnabled() { return enabled; } @@ -73,22 +65,4 @@ public String getPreferredModel() { public void setPreferredModel(@Nullable String preferredModel) { this.preferredModel = preferredModel; } - - @Nullable - public Integer getRateLimit() { - return rateLimit; - } - - public void setRateLimit(@Nullable Integer rateLimit) { - this.rateLimit = rateLimit; - } - - @Nullable - public Integer getRateLimitTimeframeHours() { - return rateLimitTimeframeHours; - } - - public void setRateLimitTimeframeHours(@Nullable Integer rateLimitTimeframeHours) { - this.rateLimitTimeframeHours = rateLimitTimeframeHours; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisRateLimitService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/IrisRateLimitService.java index 02265c7261f9..33339ad7696b 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/IrisRateLimitService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/IrisRateLimitService.java @@ -9,6 +9,7 @@ import de.tum.in.www1.artemis.domain.User; import de.tum.in.www1.artemis.repository.iris.IrisMessageRepository; import de.tum.in.www1.artemis.service.iris.exception.IrisRateLimitExceededException; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; /** * Service for the rate limit of the iris chatbot. From bfaa51a6cc4992d09938b23c06ee2de7e0487bf9 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Thu, 19 Oct 2023 21:35:54 +0200 Subject: [PATCH 03/30] Save work --- .../de/tum/in/www1/artemis/domain/Course.java | 13 -- .../artemis/domain/ProgrammingExercise.java | 13 -- .../iris/settings/IrisCourseSettings.java | 4 +- .../iris/settings/IrisExerciseSettings.java | 12 +- .../iris/settings/IrisGlobalSettings.java | 2 + .../domain/iris/settings/IrisSettings.java | 4 + .../domain/iris/settings/IrisSubSettings.java | 4 +- .../iris/IrisSettingsRepository.java | 30 ++--- .../iris/IrisCombinedChatSubSettingsDTO.java | 10 ++ .../dto/iris/IrisCombinedSettingsDTO.java | 3 +- .../iris/settings/IrisSettingsService.java | 74 +++++++++--- .../iris/settings/IrisSubSettingsService.java | 110 +++++++++++++---- .../ProgrammingExerciseService.java | 7 +- .../www1/artemis/web/rest/CourseResource.java | 1 - .../admin/iris/AdminIrisSettingsResource.java | 2 +- .../web/rest/hestia/CodeHintResource.java | 3 +- .../web/rest/iris/IrisSessionResource.java | 11 +- .../web/rest/iris/IrisSettingsResource.java | 25 ++-- .../changelog/20231019191919_changelog.xml | 73 ++++++++++++ .../resources/config/liquibase/master.xml | 1 + .../artemis/course/CourseTestService.java | 21 ---- ...rseBitbucketBambooJiraIntegrationTest.java | 6 - .../CourseGitlabJenkinsIntegrationTest.java | 6 - .../iris/AbstractIrisIntegrationTest.java | 35 +++--- .../iris/IrisHestiaIntegrationTest.java | 1 + .../iris/IrisMessageIntegrationTest.java | 6 +- .../IrisSessionActivationIntegrationTest.java | 1 + .../iris/IrisSessionIntegrationTest.java | 8 +- .../settings/IrisSettingsIntegrationTest.java | 111 +++++++++--------- 29 files changed, 376 insertions(+), 221 deletions(-) create mode 100644 src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Course.java b/src/main/java/de/tum/in/www1/artemis/domain/Course.java index be230de89c1b..ce8e9c22bdec 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Course.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Course.java @@ -26,7 +26,6 @@ import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage; import de.tum.in.www1.artemis.domain.exam.Exam; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; import de.tum.in.www1.artemis.domain.metis.Post; import de.tum.in.www1.artemis.domain.tutorialgroups.TutorialGroup; import de.tum.in.www1.artemis.domain.tutorialgroups.TutorialGroupsConfiguration; @@ -255,10 +254,6 @@ public class Course extends DomainObject { @JsonIgnoreProperties("course") private TutorialGroupsConfiguration tutorialGroupsConfiguration; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - @JoinColumn(name = "iris_settings_id") - private IrisSettings irisSettings; - // NOTE: Helpers variable names must be different from Getter name, so that Jackson ignores the @Transient annotation, but Hibernate still respects it @Transient private Long numberOfInstructorsTransient; @@ -1030,12 +1025,4 @@ public String getCourseInformationSharingMessagingCodeOfConduct() { public void setCourseInformationSharingMessagingCodeOfConduct(String courseInformationSharingMessagingCodeOfConduct) { this.courseInformationSharingMessagingCodeOfConduct = courseInformationSharingMessagingCodeOfConduct; } - - public IrisSettings getIrisSettings() { - return irisSettings; - } - - public void setIrisSettings(IrisSettings irisSettings) { - this.irisSettings = irisSettings; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java b/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java index 134a39ed56d1..74c5da833ffe 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/ProgrammingExercise.java @@ -21,7 +21,6 @@ import de.tum.in.www1.artemis.domain.enumeration.*; import de.tum.in.www1.artemis.domain.hestia.ExerciseHint; import de.tum.in.www1.artemis.domain.hestia.ProgrammingExerciseTask; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; import de.tum.in.www1.artemis.domain.participation.Participation; import de.tum.in.www1.artemis.domain.participation.SolutionProgrammingExerciseParticipation; import de.tum.in.www1.artemis.domain.participation.StudentParticipation; @@ -143,10 +142,6 @@ public String getType() { @Column(name = "release_tests_with_example_solution", table = "programming_exercise_details") private boolean releaseTestsWithExampleSolution; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - @JoinColumn(name = "iris_settings_id", table = "programming_exercise_details") - private IrisSettings irisSettings; - /** * This boolean flag determines whether the solution repository should be checked out during the build (additional to the student's submission). * This property is only used when creating the exercise (the client sets this value when POSTing the new exercise to the server). @@ -855,10 +850,6 @@ public void generateAndSetBuildPlanAccessSecret() { buildPlanAccessSecret = UUID.randomUUID().toString(); } - public IrisSettings getIrisSettings() { - return irisSettings; - } - /** * {@inheritDoc} */ @@ -868,8 +859,4 @@ public void disconnectRelatedEntities() { super.disconnectRelatedEntities(); } - - public void setIrisSettings(IrisSettings irisSettings) { - this.irisSettings = irisSettings; - } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java index d83cc00d4e02..fc59ae92938c 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java @@ -16,7 +16,7 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisCourseSettings extends IrisSettings { - @OneToOne(fetch = FetchType.LAZY, optional = false) + @OneToOne(optional = false) @JoinColumn(name = "course_id", unique = true, nullable = false) private Course course; @@ -61,10 +61,12 @@ public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { this.irisHestiaSettings = irisHestiaSettings; } + @Override public IrisCodeEditorSubSettings getIrisCodeEditorSettings() { return irisCodeEditorSettings; } + @Override public void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings) { this.irisCodeEditorSettings = irisCodeEditorSettings; } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java index 4b3df9985fe9..29460b225942 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java @@ -16,7 +16,7 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisExerciseSettings extends IrisSettings { - @OneToOne(fetch = FetchType.LAZY, optional = false) + @OneToOne(optional = false) @JoinColumn(name = "exercise_id", unique = true, nullable = false) private Exercise exercise; @@ -54,4 +54,14 @@ public IrisHestiaSubSettings getIrisHestiaSettings() { public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { } + + @Override + public IrisCodeEditorSubSettings getIrisCodeEditorSettings() { + return null; + } + + @Override + public void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings) { + + } } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java index 01691fe640a2..fa485889da86 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java @@ -97,10 +97,12 @@ public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { this.irisHestiaSettings = irisHestiaSettings; } + @Override public IrisCodeEditorSubSettings getIrisCodeEditorSettings() { return irisCodeEditorSettings; } + @Override public void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings) { this.irisCodeEditorSettings = irisCodeEditorSettings; } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java index 454c97bfbaa0..b38dae4c9cb6 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java @@ -35,5 +35,9 @@ public abstract class IrisSettings extends DomainObject { public abstract void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings); + public abstract IrisCodeEditorSubSettings getIrisCodeEditorSettings(); + + public abstract void setIrisCodeEditorSettings(IrisCodeEditorSubSettings irisCodeEditorSettings); + public abstract boolean isValid(); } diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java index 3404404f24e0..ad7547adef9b 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java @@ -1,7 +1,7 @@ package de.tum.in.www1.artemis.domain.iris.settings; -import java.util.HashSet; import java.util.Set; +import java.util.TreeSet; import javax.annotation.Nullable; import javax.persistence.*; @@ -35,7 +35,7 @@ public abstract class IrisSubSettings extends DomainObject { @Column(name = "allowed_models") @Convert(converter = IrisModelListConverter.class) - private Set allowedModels = new HashSet<>(); + private Set allowedModels = new TreeSet<>(); @Nullable @Column(name = "preferred_model") diff --git a/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java index 2e6f7ec98525..fc402041bc34 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/iris/IrisSettingsRepository.java @@ -1,5 +1,6 @@ package de.tum.in.www1.artemis.repository.iris; +import java.util.Comparator; import java.util.Optional; import java.util.Set; @@ -16,45 +17,32 @@ public interface IrisSettingsRepository extends JpaRepository findAllGlobalSettings(); - @Query(""" - SELECT irisSettings - FROM IrisSettings irisSettings - LEFT JOIN FETCH irisSettings.irisChatSettings ics - LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs - WHERE type(irisSettings) = IrisGlobalSettings - ORDER BY irisSettings.id DESC - LIMIT 1 - """) - Optional findGlobalSettings(); - default IrisGlobalSettings findGlobalSettingsElseThrow() { - return findGlobalSettings().orElseThrow(() -> new EntityNotFoundException("Iris Global Settings")); + return findAllGlobalSettings().stream().max(Comparator.comparingLong(IrisGlobalSettings::getId)).orElseThrow(() -> new EntityNotFoundException("Iris Global Settings")); } @Query(""" SELECT irisSettings - FROM IrisSettings irisSettings + FROM IrisCourseSettings irisSettings LEFT JOIN FETCH irisSettings.irisChatSettings ics LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs - WHERE type(irisSettings) = IrisCourseSettings - AND irisSettings.course.id = :courseId + LEFT JOIN FETCH irisSettings.irisCodeEditorSettings ices + WHERE irisSettings.course.id = :courseId """) Optional findCourseSettings(Long courseId); @Query(""" SELECT irisSettings - FROM IrisSettings irisSettings + FROM IrisExerciseSettings irisSettings LEFT JOIN FETCH irisSettings.irisChatSettings ics - LEFT JOIN FETCH irisSettings.irisHestiaSettings ihs - WHERE type(irisSettings) = IrisExerciseSettings - AND irisSettings.exercise.id = :exerciseId + WHERE irisSettings.exercise.id = :exerciseId """) Optional findExerciseSettings(Long exerciseId); diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java index 79781ca4653b..3539b3f6a643 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedChatSubSettingsDTO.java @@ -10,6 +10,8 @@ public class IrisCombinedChatSubSettingsDTO { private Integer rateLimit; + private Integer rateLimitTimeframeHours; + private Set allowedModels; private String preferredModel; @@ -32,6 +34,14 @@ public void setRateLimit(Integer rateLimit) { this.rateLimit = rateLimit; } + public Integer getRateLimitTimeframeHours() { + return rateLimitTimeframeHours; + } + + public void setRateLimitTimeframeHours(Integer rateLimitTimeframeHours) { + this.rateLimitTimeframeHours = rateLimitTimeframeHours; + } + public Set getAllowedModels() { return allowedModels; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java index b0660455d6c1..121db800b540 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java +++ b/src/main/java/de/tum/in/www1/artemis/service/dto/iris/IrisCombinedSettingsDTO.java @@ -1,4 +1,5 @@ package de.tum.in.www1.artemis.service.dto.iris; -public record IrisCombinedSettingsDTO(IrisCombinedChatSubSettingsDTO irisChatSettings, IrisCombinedHestiaSubSettingsDTO irisHestiaSettings) { +public record IrisCombinedSettingsDTO(IrisCombinedChatSubSettingsDTO irisChatSettings, IrisCombinedHestiaSubSettingsDTO irisHestiaSettings, + IrisCombinedCodeEditorSubSettingsDTO irisCodeEditorSettings) { } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 7fd00a47401f..37c4179af64f 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -78,9 +78,9 @@ private void createInitialGlobalSettings() { hestiaSettings.setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); settings.setIrisHestiaSettings(hestiaSettings); - updateIrisCourseEditorSettings(settings); + updateIrisCodeEditorSettings(settings); - saveIrisSettings(settings); + irisSettingsRepository.save(settings); } /** @@ -97,14 +97,14 @@ private void autoUpdateGlobalSettings(IrisGlobalSettings settings) { settings.getIrisHestiaSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); } if (settings.isEnableAutoUpdateCodeEditor()) { - updateIrisCourseEditorSettings(settings); + updateIrisCodeEditorSettings(settings); } settings.setCurrentVersion(IrisConstants.GLOBAL_SETTINGS_VERSION); saveIrisSettings(settings); } } - private static void updateIrisCourseEditorSettings(IrisGlobalSettings settings) { + private static void updateIrisCodeEditorSettings(IrisGlobalSettings settings) { var irisCodeEditorSettings = settings.getIrisCodeEditorSettings(); if (irisCodeEditorSettings == null) { irisCodeEditorSettings = new IrisCodeEditorSubSettings(); @@ -145,16 +145,19 @@ public T saveIrisSettings(T settings) { * @param settings The Iris settings to save * @return The saved Iris settings */ - public T saveNewIrisSettings(T settings) { - if (settings.getId() != null) { - throw new BadRequestAlertException("New Iris settings cannot already have an ID", "IrisSettings", "notNew"); - } + private T saveNewIrisSettings(T settings) { if (settings instanceof IrisGlobalSettings) { throw new BadRequestAlertException("You can not create new global settings", "IrisSettings", "notGlobal"); } if (!settings.isValid()) { throw new BadRequestAlertException("New Iris settings are not valid", "IrisSettings", "notValid"); } + if (settings instanceof IrisCourseSettings courseSettings && irisSettingsRepository.findCourseSettings(courseSettings.getCourse().getId()).isPresent()) { + throw new ConflictException("Iris settings for this course already exist", "IrisSettings", "alreadyExists"); + } + if (settings instanceof IrisExerciseSettings exerciseSettings && irisSettingsRepository.findExerciseSettings(exerciseSettings.getExercise().getId()).isPresent()) { + throw new ConflictException("Iris settings for this exercise already exist", "IrisSettings", "alreadyExists"); + } return irisSettingsRepository.save(settings); } @@ -186,16 +189,25 @@ else if (existingSettings instanceof IrisExerciseSettings exerciseSettings && se private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSettings, IrisGlobalSettings settingsUpdate) { existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null)); existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null)); + existingSettings.setIrisCodeEditorSettings(irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), null)); return irisSettingsRepository.save(existingSettings); } private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSettings, IrisCourseSettings settingsUpdate) { - var parentSettings = getGlobalSettings(); + var parentSettings = getCombinedIrisGlobalSettings(); + existingSettings.setIrisChatSettings( + irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings())); + existingSettings.setIrisHestiaSettings( + irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), parentSettings.irisHestiaSettings())); + existingSettings.setIrisCodeEditorSettings( + irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), parentSettings.irisCodeEditorSettings())); return irisSettingsRepository.save(existingSettings); } private IrisExerciseSettings updateExerciseSettings(IrisExerciseSettings existingSettings, IrisExerciseSettings settingsUpdate) { - + var parentSettings = getCombinedIrisSettingsFor(existingSettings.getExercise().getCourseViaExerciseGroupOrCourseMember(), false); + existingSettings.setIrisChatSettings( + irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings())); return irisSettingsRepository.save(existingSettings); } @@ -224,7 +236,8 @@ public boolean isEnabledFor(IrisSubSettingsType type, Exercise exercise) { */ public void isEnabledForElseThrow(IrisSubSettingsType type, Exercise exercise) { if (!isEnabledFor(type, exercise)) { - throw new AccessForbiddenAlertException("The Iris " + type.name() + " feature is disabled for this exercise.", "Iris", "iris.chatDisabled"); + throw new AccessForbiddenAlertException("The Iris " + type.name() + " feature is disabled for this exercise.", "Iris", + "iris." + type.name().toLowerCase() + "Disabled"); } } @@ -232,23 +245,50 @@ public IrisCombinedSettingsDTO getCombinedIrisGlobalSettings() { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); - return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, false), irisSubSettingsService.combineHestiaSettings(settingsList, false)); + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, false), irisSubSettingsService.combineHestiaSettings(settingsList, false), + irisSubSettingsService.combineCodeEditorSettings(settingsList, false)); } public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Course course, boolean minimal) { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); - irisSettingsRepository.findCourseSettings(course.getId()).ifPresent(settingsList::add); + settingsList.add(irisSettingsRepository.findCourseSettings(course.getId()).orElse(null)); - return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal)); + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal), + irisSubSettingsService.combineCodeEditorSettings(settingsList, minimal)); } public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Exercise exercise, boolean minimal) { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); - irisSettingsRepository.findCourseSettings(exercise.getCourseViaExerciseGroupOrCourseMember().getId()).ifPresent(settingsList::add); - irisSettingsRepository.findExerciseSettings(exercise.getId()).ifPresent(settingsList::add); + settingsList.add(irisSettingsRepository.findCourseSettings(exercise.getCourseViaExerciseGroupOrCourseMember().getId()).orElse(null)); + settingsList.add(irisSettingsRepository.findExerciseSettings(exercise.getId()).orElse(null)); + + return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal), + irisSubSettingsService.combineCodeEditorSettings(settingsList, minimal)); + } + + public IrisCourseSettings getDefaultSettingsFor(Course course) { + var settings = new IrisCourseSettings(); + settings.setCourse(course); + settings.setIrisChatSettings(new IrisChatSubSettings()); + settings.setIrisHestiaSettings(new IrisHestiaSubSettings()); + settings.setIrisCodeEditorSettings(new IrisCodeEditorSubSettings()); + return settings; + } + + public IrisExerciseSettings getDefaultSettingsFor(Exercise exercise) { + var settings = new IrisExerciseSettings(); + settings.setExercise(exercise); + settings.setIrisChatSettings(new IrisChatSubSettings()); + return settings; + } + + public IrisCourseSettings getRawIrisSettingsFor(Course course) { + return irisSettingsRepository.findCourseSettings(course.getId()).orElse(getDefaultSettingsFor(course)); + } - return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal)); + public IrisExerciseSettings getRawIrisSettingsFor(Exercise exercise) { + return irisSettingsRepository.findExerciseSettings(exercise.getId()).orElse(getDefaultSettingsFor(exercise)); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index 92d65440f521..c3601d299335 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -1,8 +1,6 @@ package de.tum.in.www1.artemis.service.iris.settings; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Set; +import java.util.*; import java.util.function.Function; import org.springframework.stereotype.Service; @@ -27,11 +25,20 @@ public IrisSubSettingsService(AuthorizationCheckService authCheckService) { } public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings) { + if (newSettings == null) { + if (parentSettings == null) { + throw new IllegalArgumentException("Cannot delete the chat settings"); + } + return null; + } if (currentSettings == null) { currentSettings = new IrisChatSubSettings(); } currentSettings.setEnabled(newSettings.isEnabled()); - currentSettings.setRateLimit(newSettings.getRateLimit()); + if (authCheckService.isAdmin()) { + currentSettings.setRateLimit(newSettings.getRateLimit()); + currentSettings.setRateLimitTimeframeHours(newSettings.getRateLimitTimeframeHours()); + } currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), parentSettings != null ? parentSettings.getAllowedModels() : null)); @@ -40,6 +47,12 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS } public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings) { + if (newSettings == null) { + if (parentSettings == null) { + throw new IllegalArgumentException("Cannot delete the Hestia settings"); + } + return null; + } if (currentSettings == null) { currentSettings = new IrisHestiaSubSettings(); } @@ -52,6 +65,12 @@ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisH } public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings) { + if (newSettings == null) { + if (parentSettings == null) { + throw new IllegalArgumentException("Cannot delete the Code Editor settings"); + } + return null; + } if (currentSettings == null) { currentSettings = new IrisCodeEditorSubSettings(); } @@ -123,8 +142,9 @@ public IrisCombinedChatSubSettingsDTO combineChatSettings(ArrayList settingsList, boolean minimal) { + var actualSettingsList = settingsList.stream().filter(settings -> !(settings instanceof IrisExerciseSettings)).toList(); var combinedHestiaSettings = new IrisCombinedHestiaSubSettingsDTO(); - combinedHestiaSettings.setEnabled(getCombinedEnabled(settingsList, IrisSettings::getIrisHestiaSettings)); - combinedHestiaSettings.setPreferredModel(getCombinedPreferredModel(settingsList, IrisSettings::getIrisHestiaSettings)); + combinedHestiaSettings.setEnabled(getCombinedEnabled(actualSettingsList, IrisSettings::getIrisHestiaSettings)); if (!minimal) { - combinedHestiaSettings.setTemplate(getCombinedTemplate(settingsList, IrisSettings::getIrisHestiaSettings)); + combinedHestiaSettings.setAllowedModels(getCombinedAllowedModels(actualSettingsList, IrisSettings::getIrisHestiaSettings)); + combinedHestiaSettings.setPreferredModel(getCombinedPreferredModel(actualSettingsList, IrisSettings::getIrisHestiaSettings)); + combinedHestiaSettings.setTemplate(getCombinedTemplate(actualSettingsList, IrisSettings::getIrisHestiaSettings, IrisHestiaSubSettings::getTemplate)); } return combinedHestiaSettings; } + /** + * Combines the Code Editor settings of multiple {@link IrisSettings} objects. + * If minimal is true, the returned object will only contain the enabled field. + * The minimal version can safely be sent to students. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param minimal Whether to return a minimal version of the combined settings. + * @return Combined Code Editor settings. + */ + public IrisCombinedCodeEditorSubSettingsDTO combineCodeEditorSettings(ArrayList settingsList, boolean minimal) { + var actualSettingsList = settingsList.stream().filter(settings -> !(settings instanceof IrisExerciseSettings)).toList(); + var combinedCodeEditorSettings = new IrisCombinedCodeEditorSubSettingsDTO(); + combinedCodeEditorSettings.setEnabled(getCombinedEnabled(actualSettingsList, IrisSettings::getIrisHestiaSettings)); + if (!minimal) { + combinedCodeEditorSettings.setAllowedModels(getCombinedAllowedModels(actualSettingsList, IrisSettings::getIrisHestiaSettings)); + combinedCodeEditorSettings.setPreferredModel(getCombinedPreferredModel(actualSettingsList, IrisSettings::getIrisHestiaSettings)); + + combinedCodeEditorSettings + .setChatTemplate(getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCodeEditorSettings, IrisCodeEditorSubSettings::getChatTemplate)); + combinedCodeEditorSettings.setProblemStatementGenerationTemplate( + getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCodeEditorSettings, IrisCodeEditorSubSettings::getProblemStatementGenerationTemplate)); + combinedCodeEditorSettings.setTemplateRepoGenerationTemplate( + getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCodeEditorSettings, IrisCodeEditorSubSettings::getTemplateRepoGenerationTemplate)); + combinedCodeEditorSettings.setSolutionRepoGenerationTemplate( + getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCodeEditorSettings, IrisCodeEditorSubSettings::getSolutionRepoGenerationTemplate)); + combinedCodeEditorSettings.setTestRepoGenerationTemplate( + getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCodeEditorSettings, IrisCodeEditorSubSettings::getTestRepoGenerationTemplate)); + } + return combinedCodeEditorSettings; + } + /** * Combines the enabled field of multiple {@link IrisSettings} objects. * Simply &&s all enabled fields together. @@ -156,8 +209,17 @@ public IrisCombinedHestiaSubSettingsDTO combineHestiaSettings(ArrayList settingsList, Function subSettingsFunction) { - return settingsList.stream().map(subSettingsFunction).allMatch(IrisSubSettings::isEnabled); + private boolean getCombinedEnabled(List settingsList, Function subSettingsFunction) { + for (var irisSettings : settingsList) { + if (irisSettings == null) { + return false; + } + var settings = subSettingsFunction.apply(irisSettings); + if (settings == null || !settings.isEnabled()) { + return false; + } + } + return true; } /** @@ -167,9 +229,14 @@ private boolean getCombinedEnabled(ArrayList settingsList, Functio * @param settingsList List of {@link IrisSettings} objects to combine. * @return Combined rateLimit field. */ - private Integer getCombinedRateLimit(ArrayList settingsList) { - return settingsList.stream().map(IrisSettings::getIrisChatSettings).map(IrisChatSubSettings::getRateLimit).filter(rateLimit -> rateLimit != null && rateLimit >= 0) - .min(Comparator.comparingInt(Integer::intValue)).orElse(null); + private Integer getCombinedRateLimit(List settingsList) { + return settingsList.stream().filter(Objects::nonNull).map(IrisSettings::getIrisChatSettings).filter(Objects::nonNull).map(IrisChatSubSettings::getRateLimit) + .filter(rateLimit -> rateLimit != null && rateLimit >= 0).min(Comparator.comparingInt(Integer::intValue)).orElse(null); + } + + private Set getCombinedAllowedModels(List settingsList, Function subSettingsFunction) { + return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getAllowedModels).filter(Objects::nonNull) + .reduce((first, second) -> second).orElse(new TreeSet<>()); } /** @@ -181,21 +248,22 @@ private Integer getCombinedRateLimit(ArrayList settingsList) { * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. * @return Combined preferredModel field. */ - private String getCombinedPreferredModel(ArrayList settingsList, Function subSettingsFunction) { - return settingsList.stream().map(subSettingsFunction).map(IrisSubSettings::getPreferredModel).filter(model -> model != null && !model.isBlank()) - .reduce((first, second) -> second).orElseThrow(); + private String getCombinedPreferredModel(List settingsList, Function subSettingsFunction) { + return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getPreferredModel) + .filter(model -> model != null && !model.isBlank()).reduce((first, second) -> second).orElse(null); } /** * Combines the template field of multiple {@link IrisSettings} objects. * Simply takes the last template. * - * @param settingsList List of {@link IrisSettings} objects to combine. - * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param templateFunction Function to get the template from the sub settings from an IrisSettings object. * @return Combined template field. */ - private IrisTemplate getCombinedTemplate(ArrayList settingsList, Function subSettingsFunction) { - return settingsList.stream().map(subSettingsFunction).map(IrisSubSettings::getTemplate).filter(template -> template != null && !template.getContent().isBlank()) - .reduce((first, second) -> second).orElseThrow(); + private IrisTemplate getCombinedTemplate(List settingsList, Function subSettingsFunction, + Function templateFunction) { + return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(templateFunction) + .filter(template -> template != null && !template.getContent().isBlank()).reduce((first, second) -> second).orElse(null); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java index 3805305e6917..7b1b596e87c7 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java @@ -44,7 +44,6 @@ import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationTriggerService; import de.tum.in.www1.artemis.service.connectors.vcs.VersionControlService; import de.tum.in.www1.artemis.service.hestia.ProgrammingExerciseTaskService; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; import de.tum.in.www1.artemis.service.messaging.InstanceMessageSendService; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; @@ -131,8 +130,6 @@ public class ProgrammingExerciseService { private final ProgrammingSubmissionService programmingSubmissionService; - private final IrisSettingsService irisSettingsService; - public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerciseRepository, GitService gitService, Optional versionControlService, Optional continuousIntegrationService, Optional continuousIntegrationTriggerService, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository, @@ -144,7 +141,7 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository, ExerciseSpecificationService exerciseSpecificationService, ProgrammingExerciseRepositoryService programmingExerciseRepositoryService, AuxiliaryRepositoryService auxiliaryRepositoryService, SubmissionPolicyService submissionPolicyService, Optional programmingLanguageFeatureService, ChannelService channelService, - ProgrammingSubmissionService programmingSubmissionService, IrisSettingsService irisSettingsService) { + ProgrammingSubmissionService programmingSubmissionService) { this.programmingExerciseRepository = programmingExerciseRepository; this.gitService = gitService; this.versionControlService = versionControlService; @@ -171,7 +168,6 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc this.programmingLanguageFeatureService = programmingLanguageFeatureService; this.channelService = channelService; this.programmingSubmissionService = programmingSubmissionService; - this.irisSettingsService = irisSettingsService; } /** @@ -450,7 +446,6 @@ public ProgrammingExercise updateProgrammingExercise(ProgrammingExercise program connectAuxiliaryRepositoriesToExercise(updatedProgrammingExercise); channelService.updateExerciseChannel(programmingExerciseBeforeUpdate, updatedProgrammingExercise); - irisSettingsService.updateIrisSettings(programmingExerciseBeforeUpdate, updatedProgrammingExercise); ProgrammingExercise savedProgrammingExercise = programmingExerciseRepository.save(updatedProgrammingExercise); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java index 5fc8d4b36b10..a157d65ab377 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java @@ -215,7 +215,6 @@ public ResponseEntity updateCourse(@PathVariable Long courseId, @Request courseUpdate.setPrerequisites(existingCourse.getPrerequisites()); courseUpdate.setTutorialGroupsConfiguration(existingCourse.getTutorialGroupsConfiguration()); courseUpdate.setOnlineCourseConfiguration(existingCourse.getOnlineCourseConfiguration()); - courseUpdate.setIrisSettings(existingCourse.getIrisSettings()); courseUpdate.validateEnrollmentConfirmationMessage(); courseUpdate.validateComplaintsAndRequestMoreFeedbackConfig(); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java index ce0f9e00eef4..0eba50c5b866 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java @@ -32,7 +32,7 @@ public AdminIrisSettingsResource(IrisSettingsService irisSettingsService) { @PutMapping("iris/global-iris-settings") @EnforceAdmin public ResponseEntity updateGlobalSettings(@RequestBody IrisSettings settings) { - var updatedSettings = irisSettingsService.saveGlobalIrisSettings(settings); + var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java index a1fec3e5fd55..b2339f5c4ca5 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java @@ -11,6 +11,7 @@ import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.hestia.CodeHint; +import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettingsType; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; import de.tum.in.www1.artemis.repository.hestia.CodeHintRepository; import de.tum.in.www1.artemis.repository.hestia.ProgrammingExerciseSolutionEntryRepository; @@ -109,7 +110,7 @@ public ResponseEntity generateDescriptionForCodeHint(@PathVariable Lon ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - irisSettingsService.checkIsIrisHestiaSessionEnabledElseThrow(exercise); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.HESTIA, exercise); // Hints for exam exercises are not supported at the moment if (exercise.isExamExercise()) { diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java index 146bea631beb..0f4b420aaa7f 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSessionResource.java @@ -11,6 +11,7 @@ import de.tum.in.www1.artemis.domain.ProgrammingExercise; import de.tum.in.www1.artemis.domain.iris.session.IrisSession; +import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettingsType; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; import de.tum.in.www1.artemis.repository.UserRepository; import de.tum.in.www1.artemis.repository.iris.IrisChatSessionRepository; @@ -70,7 +71,7 @@ public IrisSessionResource(ProgrammingExerciseRepository programmingExerciseRepo @EnforceAtLeastStudent public ResponseEntity getCurrentSession(@PathVariable Long exerciseId) { ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - irisSettingsService.checkIsIrisChatSessionEnabledElseThrow(exercise); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.CHAT, exercise); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); @@ -89,7 +90,7 @@ public ResponseEntity getCurrentSession(@PathVariable Long exercise @EnforceAtLeastStudent public ResponseEntity> getAllSessions(@PathVariable Long exerciseId) { ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - irisSettingsService.checkIsIrisChatSessionEnabledElseThrow(exercise); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.CHAT, exercise); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); @@ -110,7 +111,7 @@ public ResponseEntity> getAllSessions(@PathVariable Long exerc @EnforceAtLeastStudent public ResponseEntity createSessionForProgrammingExercise(@PathVariable Long exerciseId) throws URISyntaxException { ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - irisSettingsService.checkIsIrisChatSessionEnabledElseThrow(exercise); + irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.CHAT, exercise); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); @@ -134,12 +135,12 @@ public ResponseEntity isIrisActive(@PathVariable Long sessionId) var user = userRepository.getUser(); irisSessionService.checkHasAccessToIrisSession(session, user); irisSessionService.checkIsIrisActivated(session); - var settings = irisSettingsService.getCombinedIrisSettings(session.getExercise(), false); + var settings = irisSettingsService.getCombinedIrisSettingsFor(session.getExercise(), false); var health = irisHealthIndicator.health(); IrisStatusDTO[] modelStatuses = (IrisStatusDTO[]) health.getDetails().get("modelStatuses"); var specificModelStatus = false; if (modelStatuses != null) { - specificModelStatus = Arrays.stream(modelStatuses).filter(x -> x.model().equals(settings.getIrisChatSettings().getPreferredModel())) + specificModelStatus = Arrays.stream(modelStatuses).filter(x -> x.model().equals(settings.irisChatSettings().getPreferredModel())) .anyMatch(x -> x.status() == IrisStatusDTO.ModelStatus.UP); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java index 4ae2431904e6..cdc51d6190f6 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java @@ -3,6 +3,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import de.tum.in.www1.artemis.domain.iris.settings.IrisCourseSettings; +import de.tum.in.www1.artemis.domain.iris.settings.IrisExerciseSettings; import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; import de.tum.in.www1.artemis.repository.CourseRepository; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; @@ -10,6 +12,7 @@ import de.tum.in.www1.artemis.security.Role; import de.tum.in.www1.artemis.security.annotations.*; import de.tum.in.www1.artemis.service.AuthorizationCheckService; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedSettingsDTO; import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; /** @@ -61,7 +64,7 @@ public ResponseEntity getGlobalSettings() { public ResponseEntity getRawCourseSettings(@PathVariable Long courseId) { var course = courseRepository.findByIdElseThrow(courseId); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, null); - var irisSettings = irisSettingsService.getIrisSettingsOrDefault(course); + var irisSettings = irisSettingsService.getRawIrisSettingsFor(course); return ResponseEntity.ok(irisSettings); } @@ -78,7 +81,7 @@ public ResponseEntity getRawProgrammingExerciseSettings(@PathVaria var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); - var combinedIrisSettings = irisSettingsService.getIrisSettingsOrDefault(exercise); + var combinedIrisSettings = irisSettingsService.getRawIrisSettingsFor(exercise); return ResponseEntity.ok(combinedIrisSettings); } @@ -90,14 +93,14 @@ public ResponseEntity getRawProgrammingExerciseSettings(@PathVaria */ @GetMapping("courses/{courseId}/iris-settings") @EnforceAtLeastStudent - public ResponseEntity getCourseSettings(@PathVariable Long courseId) { + public ResponseEntity getCourseSettings(@PathVariable Long courseId) { var course = courseRepository.findByIdElseThrow(courseId); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, user); // Editors can see the full settings, students only the reduced settings var getReduced = !authCheckService.isAtLeastEditorInCourse(course, user); - var irisSettings = irisSettingsService.getCombinedIrisSettings(course, getReduced); + var irisSettings = irisSettingsService.getCombinedIrisSettingsFor(course, getReduced); return ResponseEntity.ok(irisSettings); } @@ -109,14 +112,14 @@ public ResponseEntity getCourseSettings(@PathVariable Long courseI */ @GetMapping("programming-exercises/{exerciseId}/iris-settings") @EnforceAtLeastStudent - public ResponseEntity getProgrammingExerciseSettings(@PathVariable Long exerciseId) { + public ResponseEntity getProgrammingExerciseSettings(@PathVariable Long exerciseId) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); // Editors can see the full settings, students only the reduced settings var getReduced = !authCheckService.isAtLeastEditorForExercise(exercise, user); - var combinedIrisSettings = irisSettingsService.getCombinedIrisSettings(exercise, getReduced); + var combinedIrisSettings = irisSettingsService.getCombinedIrisSettingsFor(exercise, getReduced); return ResponseEntity.ok(combinedIrisSettings); } @@ -129,10 +132,11 @@ public ResponseEntity getProgrammingExerciseSettings(@PathVariable */ @PutMapping("courses/{courseId}/raw-iris-settings") @EnforceAtLeastEditor - public ResponseEntity updateCourseSettings(@PathVariable Long courseId, @RequestBody IrisSettings settings) { + public ResponseEntity updateCourseSettings(@PathVariable Long courseId, @RequestBody IrisCourseSettings settings) { var course = courseRepository.findByIdElseThrow(courseId); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, null); - var updatedSettings = irisSettingsService.saveIrisSettings(course, settings); + settings.setCourse(course); + var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); } @@ -146,11 +150,12 @@ public ResponseEntity updateCourseSettings(@PathVariable Long cour */ @PutMapping("programming-exercises/{exerciseId}/raw-iris-settings") @EnforceAtLeastEditor - public ResponseEntity updateProgrammingExerciseSettings(@PathVariable Long exerciseId, @RequestBody IrisSettings settings) { + public ResponseEntity updateProgrammingExerciseSettings(@PathVariable Long exerciseId, @RequestBody IrisExerciseSettings settings) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); var user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, user); - var updatedSettings = irisSettingsService.saveIrisSettings(exercise, settings); + settings.setExercise(exercise); + var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); } } diff --git a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml new file mode 100644 index 000000000000..cf0e1ee28c01 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE iris_settings SET discriminator = 'GLOBAL' WHERE id IN (SELECT id FROM iris_settings WHERE is_global = TRUE); + UPDATE iris_settings SET discriminator = 'COURSE' WHERE id IN (SELECT iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL); + UPDATE iris_settings SET discriminator = 'EXERCISE' WHERE id IN (SELECT iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL); + + UPDATE iris_sub_settings SET discriminator = 'CHAT' WHERE id IN (SELECT iris_chat_settings_id FROM iris_settings WHERE iris_chat_settings_id IS NOT NULL); + UPDATE iris_sub_settings SET discriminator = 'HESTIA' WHERE id IN (SELECT iris_hestia_settings_id FROM iris_settings WHERE iris_hestia_settings_id IS NOT NULL); + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index a387a1af2d6f..24840c799a52 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -64,6 +64,7 @@ + diff --git a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java index 6a74bc01026b..b65e8527c947 100644 --- a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java +++ b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java @@ -85,7 +85,6 @@ import de.tum.in.www1.artemis.service.dto.UserPublicInfoDTO; import de.tum.in.www1.artemis.service.export.CourseExamExportService; import de.tum.in.www1.artemis.service.export.DataExportUtil; -import de.tum.in.www1.artemis.service.iris.IrisSettingsService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationService; import de.tum.in.www1.artemis.service.scheduled.ParticipantScoreScheduleService; import de.tum.in.www1.artemis.team.TeamUtilService; @@ -228,9 +227,6 @@ public class CourseTestService { @Autowired private ParticipantScoreScheduleService participantScoreScheduleService; - @Autowired - private IrisSettingsService irisSettingsService; - @Autowired private QuizExerciseUtilService quizExerciseUtilService; @@ -624,23 +620,6 @@ public void testEditCourseShouldPreserveAssociations() throws Exception { assertThat(updatedCourse.getPrerequisites()).containsExactlyElementsOf(prerequisites); } - // Test - public void testEditCourseShouldPreserveIrisSettings() throws Exception { - Course course = courseUtilService.createCourseWithOrganizations(); - course = courseRepo.save(course); - - var courseWithSettings = courseRepo.findByIdElseThrow(course.getId()); - courseWithSettings = irisSettingsService.addDefaultIrisSettingsTo(courseWithSettings); - courseWithSettings.getIrisSettings().getIrisChatSettings().setEnabled(true); - courseWithSettings.getIrisSettings().getIrisChatSettings().setPreferredModel(null); - courseRepo.save(courseWithSettings); - - request.getMvc().perform(buildUpdateCourse(course.getId(), course)).andExpect(status().isOk()); - - Course updatedCourse = courseRepo.findByIdForUpdateElseThrow(course.getId()); - assertThat(updatedCourse.getIrisSettings()).isEqualTo(courseWithSettings.getIrisSettings()); - } - // Test public void testUpdateCourseGroups() throws Exception { Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise(); diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java index 94e289d0a201..b50ac0b2f696 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java @@ -172,12 +172,6 @@ void testEditCourseShouldPreserveAssociations() throws Exception { courseTestService.testEditCourseShouldPreserveAssociations(); } - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditCourseShouldPreserveIrisSettings() throws Exception { - courseTestService.testEditCourseShouldPreserveIrisSettings(); - } - @Test @WithMockUser(username = "admin", roles = "ADMIN") void testUpdateCourseGroups() throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java index 8520826dc12a..603181228f25 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java @@ -171,12 +171,6 @@ void testEditCourseShouldPreserveAssociations() throws Exception { courseTestService.testEditCourseShouldPreserveAssociations(); } - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditCourseShouldPreserveIrisSettings() throws Exception { - courseTestService.testEditCourseShouldPreserveIrisSettings(); - } - @Test @WithMockUser(username = "admin", roles = "ADMIN") void testUpdateCourseGroups() throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java index 752915740311..f0461c59e77c 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java @@ -75,26 +75,33 @@ protected void activateIrisGlobally() { globalSettings.getIrisChatSettings().setPreferredModel(null); globalSettings.getIrisHestiaSettings().setEnabled(true); globalSettings.getIrisHestiaSettings().setPreferredModel(null); - irisSettingsService.saveGlobalIrisSettings(globalSettings); + irisSettingsService.saveIrisSettings(globalSettings); } protected void activateIrisFor(Course course) { - var courseWithSettings = irisSettingsService.addDefaultIrisSettingsTo(course); - courseWithSettings.getIrisSettings().getIrisChatSettings().setEnabled(true); - courseWithSettings.getIrisSettings().getIrisChatSettings().setTemplate(createDummyTemplate()); - courseWithSettings.getIrisSettings().getIrisChatSettings().setPreferredModel(null); - courseWithSettings.getIrisSettings().getIrisHestiaSettings().setEnabled(true); - courseWithSettings.getIrisSettings().getIrisHestiaSettings().setTemplate(createDummyTemplate()); - courseWithSettings.getIrisSettings().getIrisHestiaSettings().setPreferredModel(null); - courseRepository.save(courseWithSettings); + var courseSettings = irisSettingsService.getDefaultSettingsFor(course); + courseSettings.getIrisChatSettings().setEnabled(true); + courseSettings.getIrisChatSettings().setTemplate(createDummyTemplate()); + courseSettings.getIrisChatSettings().setPreferredModel(null); + courseSettings.getIrisHestiaSettings().setEnabled(true); + courseSettings.getIrisHestiaSettings().setTemplate(createDummyTemplate()); + courseSettings.getIrisHestiaSettings().setPreferredModel(null); + courseSettings.getIrisCodeEditorSettings().setEnabled(true); + courseSettings.getIrisCodeEditorSettings().setChatTemplate(createDummyTemplate()); + courseSettings.getIrisCodeEditorSettings().setProblemStatementGenerationTemplate(createDummyTemplate()); + courseSettings.getIrisCodeEditorSettings().setTemplateRepoGenerationTemplate(null); + courseSettings.getIrisCodeEditorSettings().setSolutionRepoGenerationTemplate(null); + courseSettings.getIrisCodeEditorSettings().setTestRepoGenerationTemplate(null); + courseSettings.getIrisCodeEditorSettings().setPreferredModel(null); + irisSettingsService.saveIrisSettings(courseSettings); } protected void activateIrisFor(ProgrammingExercise exercise) { - var exerciseWithSettings = irisSettingsService.addDefaultIrisSettingsTo(exercise); - exerciseWithSettings.getIrisSettings().getIrisChatSettings().setEnabled(true); - exerciseWithSettings.getIrisSettings().getIrisChatSettings().setTemplate(createDummyTemplate()); - exerciseWithSettings.getIrisSettings().getIrisChatSettings().setPreferredModel(null); - programmingExerciseRepository.save(exerciseWithSettings); + var exerciseSettings = irisSettingsService.getDefaultSettingsFor(exercise); + exerciseSettings.getIrisChatSettings().setEnabled(true); + exerciseSettings.getIrisChatSettings().setTemplate(createDummyTemplate()); + exerciseSettings.getIrisChatSettings().setPreferredModel(null); + irisSettingsService.saveIrisSettings(exerciseSettings); } protected IrisTemplate createDummyTemplate() { diff --git a/src/test/java/de/tum/in/www1/artemis/iris/IrisHestiaIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/IrisHestiaIntegrationTest.java index bc72a7aa3e31..01c3ada4b1d6 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/IrisHestiaIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/IrisHestiaIntegrationTest.java @@ -30,6 +30,7 @@ void initTestCase() { final Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases(); exercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class); + activateIrisGlobally(); activateIrisFor(course); activateIrisFor(exercise); } diff --git a/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java index 3f26ddb6170e..faff5e1adcd7 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java @@ -67,6 +67,7 @@ void initTestCase() { final Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases(); exercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class); + activateIrisGlobally(); activateIrisFor(course); activateIrisFor(exercise); repository = new LocalRepository("main"); @@ -296,7 +297,7 @@ void sendMessageRateLimitReached() throws Exception { var globalSettings = irisSettingsService.getGlobalSettings(); globalSettings.getIrisChatSettings().setRateLimit(1); globalSettings.getIrisChatSettings().setRateLimitTimeframeHours(10); - irisSettingsService.saveGlobalIrisSettings(globalSettings); + irisSettingsService.saveIrisSettings(globalSettings); request.postWithResponseBody("/api/iris/sessions/" + irisSession.getId() + "/messages", messageToSend1, IrisMessage.class, HttpStatus.CREATED); await().until(() -> irisSessionRepository.findByIdWithMessagesElseThrow(irisSession.getId()).getMessages().size() == 2); @@ -311,14 +312,13 @@ void sendMessageRateLimitReached() throws Exception { // Reset to not interfere with other tests globalSettings.getIrisChatSettings().setRateLimit(null); globalSettings.getIrisChatSettings().setRateLimitTimeframeHours(null); - irisSettingsService.saveGlobalIrisSettings(globalSettings); + irisSettingsService.saveIrisSettings(globalSettings); } private void setupExercise() throws Exception { var savedExercise = irisUtilTestService.setupTemplate(exercise, repository); var exerciseParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(savedExercise, TEST_PREFIX + "student1"); irisUtilTestService.setupStudentParticipation(exerciseParticipation, repository); - activateIrisFor(savedExercise); } private IrisMessage createDefaultMockMessage(IrisSession irisSession) { diff --git a/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionActivationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionActivationIntegrationTest.java index fb5ed418b12f..589110fa5453 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionActivationIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionActivationIntegrationTest.java @@ -37,6 +37,7 @@ void initTestCase() { final Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases(); exercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class); + activateIrisGlobally(); activateIrisFor(course); } diff --git a/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionIntegrationTest.java index a84fd55b6d17..d7fc6f98d995 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/IrisSessionIntegrationTest.java @@ -77,16 +77,16 @@ void isActive() throws Exception { var previousPreferredModel = settings.getIrisChatSettings().getPreferredModel(); settings.getIrisChatSettings().setPreferredModel("TEST_MODEL_UP"); - irisSettingsService.saveGlobalIrisSettings(settings); + irisSettingsService.saveIrisSettings(settings); assertThat(request.get("/api/iris/sessions/" + irisSession.getId() + "/active", HttpStatus.OK, IrisHealthDTO.class).active()).isTrue(); settings.getIrisChatSettings().setPreferredModel("TEST_MODEL_DOWN"); - irisSettingsService.saveGlobalIrisSettings(settings); + irisSettingsService.saveIrisSettings(settings); assertThat(request.get("/api/iris/sessions/" + irisSession.getId() + "/active", HttpStatus.OK, IrisHealthDTO.class).active()).isFalse(); settings.getIrisChatSettings().setPreferredModel("TEST_MODEL_NA"); - irisSettingsService.saveGlobalIrisSettings(settings); + irisSettingsService.saveIrisSettings(settings); assertThat(request.get("/api/iris/sessions/" + irisSession.getId() + "/active", HttpStatus.OK, IrisHealthDTO.class).active()).isFalse(); settings.getIrisChatSettings().setPreferredModel(previousPreferredModel); - irisSettingsService.saveGlobalIrisSettings(settings); + irisSettingsService.saveIrisSettings(settings); } } diff --git a/src/test/java/de/tum/in/www1/artemis/iris/settings/IrisSettingsIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/settings/IrisSettingsIntegrationTest.java index 39f292072d55..84d725911885 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/settings/IrisSettingsIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/settings/IrisSettingsIntegrationTest.java @@ -2,6 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.HashSet; +import java.util.TreeSet; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -10,11 +13,11 @@ import de.tum.in.www1.artemis.domain.Course; import de.tum.in.www1.artemis.domain.ProgrammingExercise; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSettings; -import de.tum.in.www1.artemis.domain.iris.settings.IrisSubSettings; +import de.tum.in.www1.artemis.domain.iris.settings.*; import de.tum.in.www1.artemis.iris.AbstractIrisIntegrationTest; import de.tum.in.www1.artemis.repository.iris.IrisSettingsRepository; import de.tum.in.www1.artemis.repository.iris.IrisSubSettingsRepository; +import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedSettingsDTO; class IrisSettingsIntegrationTest extends AbstractIrisIntegrationTest { @@ -43,12 +46,12 @@ void initTestCase() { void getMissingSettingsForCourse() throws Exception { activateIrisGlobally(); var loadedSettings1 = request.get("/api/courses/" + course.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - var loadedSettings2 = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings2 = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); - assertThat(loadedSettings2).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id") - .isEqualTo(irisSettingsService.getCombinedIrisSettings(course, false)); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id") - .isEqualTo(irisSettingsService.addDefaultIrisSettingsTo(course).getIrisSettings()); + assertThat(loadedSettings2).isNotNull().usingRecursiveComparison().ignoringFieldsOfTypes(HashSet.class, TreeSet.class) + .isEqualTo(irisSettingsService.getCombinedIrisSettingsFor(course, false)); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "course", "irisChatSettings.id", "irisHestiaSettings.id") + .isEqualTo(irisSettingsService.getDefaultSettingsFor(course)); } @Test @@ -59,10 +62,12 @@ void getCourseSettings() throws Exception { course = courseRepository.findByIdElseThrow(course.getId()); var loadedSettings1 = request.get("/api/courses/" + course.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - var loadedSettings2 = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings2 = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id").isEqualTo(loadedSettings2); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().isEqualTo(irisSettingsRepository.findById(course.getIrisSettings().getId()).orElseThrow()); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison() + .ignoringFields("id", "course", "irisChatSettings.id", "irisHestiaSettings.id", "irisCodeEditorSettings.id").isEqualTo(loadedSettings2); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("course") + .isEqualTo(irisSettingsRepository.findCourseSettings(course.getId()).orElseThrow()); } @Test @@ -73,9 +78,9 @@ void getCourseSettingsAsUser() throws Exception { course = courseRepository.findByIdElseThrow(course.getId()); request.get("/api/courses/" + course.getId() + "/raw-iris-settings", HttpStatus.FORBIDDEN, IrisSettings.class); - var loadedSettings = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings = request.get("/api/courses/" + course.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); - assertThat(loadedSettings).isNotNull().usingRecursiveComparison().ignoringFields("id").isEqualTo(irisSettingsService.getCombinedIrisSettings(course, true)); + assertThat(loadedSettings).isNotNull().usingRecursiveComparison().ignoringFields("id").isEqualTo(irisSettingsService.getCombinedIrisSettingsFor(course, true)); } @Test @@ -117,8 +122,8 @@ void updateCourseSettings2() throws Exception { var updatedSettings = request.putWithResponseBody("/api/courses/" + course.getId() + "/raw-iris-settings", loadedSettings1, IrisSettings.class, HttpStatus.OK); var loadedSettings2 = request.get("/api/courses/" + course.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - assertThat(updatedSettings).isNotNull().usingRecursiveComparison().isEqualTo(loadedSettings1); - assertThat(updatedSettings).isNotNull().usingRecursiveComparison().isEqualTo(loadedSettings2); + assertThat(updatedSettings).isNotNull().usingRecursiveComparison().ignoringFields("course").isEqualTo(loadedSettings1); + assertThat(updatedSettings).isNotNull().usingRecursiveComparison().ignoringFields("course").isEqualTo(loadedSettings2); // Original subsettings should not exist anymore assertThat(irisSubSettingsRepository.findById(chatSubSettingsId)).isEmpty(); assertThat(irisSubSettingsRepository.findById(hestiaSubSettingsId)).isEmpty(); @@ -130,23 +135,24 @@ void updateCourseSettings3() throws Exception { activateIrisGlobally(); course = courseRepository.findByIdElseThrow(course.getId()); - course.setIrisSettings(new IrisSettings()); - course.getIrisSettings().setIrisChatSettings(new IrisSubSettings()); - course.getIrisSettings().getIrisChatSettings().setEnabled(true); - course.getIrisSettings().getIrisChatSettings().setTemplate(createDummyTemplate()); - course.getIrisSettings().getIrisChatSettings().setPreferredModel(null); - course.getIrisSettings().setIrisHestiaSettings(new IrisSubSettings()); - course.getIrisSettings().getIrisHestiaSettings().setEnabled(true); - course.getIrisSettings().getIrisHestiaSettings().setTemplate(createDummyTemplate()); - course.getIrisSettings().getIrisHestiaSettings().setPreferredModel(null); - - var updatedSettings = request.putWithResponseBody("/api/courses/" + course.getId() + "/raw-iris-settings", course.getIrisSettings(), IrisSettings.class, HttpStatus.OK); + var courseSettings = new IrisCourseSettings(); + courseSettings.setCourse(course); + courseSettings.setIrisChatSettings(new IrisChatSubSettings()); + courseSettings.getIrisChatSettings().setEnabled(true); + courseSettings.getIrisChatSettings().setTemplate(createDummyTemplate()); + courseSettings.getIrisChatSettings().setPreferredModel(null); + courseSettings.setIrisHestiaSettings(new IrisHestiaSubSettings()); + courseSettings.getIrisHestiaSettings().setEnabled(true); + courseSettings.getIrisHestiaSettings().setTemplate(createDummyTemplate()); + courseSettings.getIrisHestiaSettings().setPreferredModel(null); + + var updatedSettings = request.putWithResponseBody("/api/courses/" + course.getId() + "/raw-iris-settings", courseSettings, IrisSettings.class, HttpStatus.OK); var loadedSettings1 = request.get("/api/courses/" + course.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - assertThat(updatedSettings).isNotNull().isEqualTo(loadedSettings1); + assertThat(updatedSettings).usingRecursiveComparison().ignoringFields("course").isEqualTo(loadedSettings1); assertThat(loadedSettings1).usingRecursiveComparison() - .ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id", "irisChatSettings.template.id", "irisHestiaSettings.template.id") - .isEqualTo(course.getIrisSettings()); + .ignoringFields("id", "course", "irisChatSettings.id", "irisHestiaSettings.id", "irisChatSettings.template.id", "irisHestiaSettings.template.id") + .isEqualTo(courseSettings); } @Test @@ -155,14 +161,14 @@ void getMissingSettingsForProgrammingExercise() throws Exception { activateIrisGlobally(); activateIrisFor(course); var loadedSettings1 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - var loadedSettings2 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings2 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); programmingExercise = programmingExerciseRepository.findByIdElseThrow(programmingExercise.getId()); assertThat(loadedSettings2).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id") - .isEqualTo(irisSettingsService.getCombinedIrisSettings(programmingExercise, false)); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id") - .isEqualTo(irisSettingsService.addDefaultIrisSettingsTo(programmingExercise).getIrisSettings()); + .ignoringFieldsOfTypes(HashSet.class, TreeSet.class).isEqualTo(irisSettingsService.getCombinedIrisSettingsFor(programmingExercise, false)); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "exercise", "irisChatSettings.id", "irisHestiaSettings.id") + .isEqualTo(irisSettingsService.getDefaultSettingsFor(programmingExercise)); } @Test @@ -174,11 +180,13 @@ void getProgrammingExerciseSettings() throws Exception { programmingExercise = programmingExerciseRepository.findByIdElseThrow(programmingExercise.getId()); var loadedSettings1 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - var loadedSettings2 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings2 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings").isEqualTo(loadedSettings2); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison() + .ignoringFields("id", "exercise", "irisChatSettings.id", "irisHestiaSettings.id", "irisCodeEditorSettings.id").isEqualTo(loadedSettings2); assertThat(loadedSettings1.getIrisHestiaSettings()).isNull(); - assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().isEqualTo(irisSettingsRepository.findById(programmingExercise.getIrisSettings().getId()).orElseThrow()); + assertThat(loadedSettings1).isNotNull().usingRecursiveComparison().ignoringFields("exercise") + .isEqualTo(irisSettingsRepository.findExerciseSettings(programmingExercise.getId()).orElseThrow()); } @Test @@ -190,9 +198,9 @@ void getProgrammingExerciseSettingsAsUser() throws Exception { programmingExercise = programmingExerciseRepository.findByIdElseThrow(programmingExercise.getId()); request.get("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", HttpStatus.FORBIDDEN, IrisSettings.class); - var loadedSettings = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisSettings.class); + var loadedSettings = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/iris-settings", HttpStatus.OK, IrisCombinedSettingsDTO.class); - assertThat(loadedSettings).isNotNull().usingRecursiveComparison().ignoringFields("id").isEqualTo(irisSettingsService.getCombinedIrisSettings(programmingExercise, true)); + assertThat(loadedSettings).isNotNull().usingRecursiveComparison().ignoringFields("id").isEqualTo(irisSettingsService.getCombinedIrisSettingsFor(programmingExercise, true)); } @Test @@ -235,8 +243,8 @@ void updateProgrammingExerciseSettings2() throws Exception { HttpStatus.OK); var loadedSettings2 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); - assertThat(updatedSettings).isNotNull().usingRecursiveComparison().isEqualTo(loadedSettings1); - assertThat(updatedSettings).isNotNull().usingRecursiveComparison().isEqualTo(loadedSettings2); + assertThat(updatedSettings).isNotNull().usingRecursiveComparison().ignoringFields("exercise").isEqualTo(loadedSettings1); + assertThat(updatedSettings).isNotNull().usingRecursiveComparison().ignoringFields("exercise").isEqualTo(loadedSettings2); // Original subsettings should not exist anymore assertThat(irisSubSettingsRepository.findById(chatSubSettingsId)).isEmpty(); } @@ -248,23 +256,20 @@ void updateProgrammingExerciseSettings3() throws Exception { activateIrisFor(course); programmingExercise = programmingExerciseRepository.findByIdElseThrow(programmingExercise.getId()); - programmingExercise.setIrisSettings(new IrisSettings()); - programmingExercise.getIrisSettings().setIrisChatSettings(new IrisSubSettings()); - programmingExercise.getIrisSettings().getIrisChatSettings().setEnabled(true); - programmingExercise.getIrisSettings().getIrisChatSettings().setTemplate(createDummyTemplate()); - programmingExercise.getIrisSettings().getIrisChatSettings().setPreferredModel(null); - programmingExercise.getIrisSettings().setIrisHestiaSettings(new IrisSubSettings()); - programmingExercise.getIrisSettings().getIrisHestiaSettings().setEnabled(true); - programmingExercise.getIrisSettings().getIrisHestiaSettings().setTemplate(createDummyTemplate()); - programmingExercise.getIrisSettings().getIrisHestiaSettings().setPreferredModel(null); - - var updatedSettings = request.putWithResponseBody("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", programmingExercise.getIrisSettings(), - IrisSettings.class, HttpStatus.OK); + var exerciseSettings = new IrisExerciseSettings(); + exerciseSettings.setExercise(programmingExercise); + exerciseSettings.setIrisChatSettings(new IrisChatSubSettings()); + exerciseSettings.getIrisChatSettings().setEnabled(true); + exerciseSettings.getIrisChatSettings().setTemplate(createDummyTemplate()); + exerciseSettings.getIrisChatSettings().setPreferredModel(null); + + var updatedSettings = request.putWithResponseBody("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", exerciseSettings, IrisSettings.class, + HttpStatus.OK); var loadedSettings1 = request.get("/api/programming-exercises/" + programmingExercise.getId() + "/raw-iris-settings", HttpStatus.OK, IrisSettings.class); assertThat(updatedSettings).isNotNull().isEqualTo(loadedSettings1); assertThat(loadedSettings1).usingRecursiveComparison() - .ignoringFields("id", "irisChatSettings.id", "irisHestiaSettings.id", "irisChatSettings.template.id", "irisHestiaSettings.template.id") - .isEqualTo(programmingExercise.getIrisSettings()); + .ignoringFields("id", "exercise", "irisChatSettings.id", "irisHestiaSettings.id", "irisChatSettings.template.id", "irisHestiaSettings.template.id") + .isEqualTo(exerciseSettings); } } From 0b5828720fa5a9d5bd6ebf56726f2071dc1dd3fb Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Thu, 19 Oct 2023 22:03:32 +0200 Subject: [PATCH 04/30] Improve migration --- .../settings/IrisCodeEditorSubSettings.java | 6 + .../changelog/20231019191919_changelog.xml | 116 ++++++++++++++---- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java index 3084cf517fc4..ee2dab8ef7f4 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java @@ -13,26 +13,32 @@ */ @Entity @DiscriminatorValue("CODE_EDITOR") +@SecondaryTable(name = "iris_code_editor_sub_settings") @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisCodeEditorSubSettings extends IrisSubSettings { @Nullable + @JoinColumn(table = "iris_code_editor_sub_settings") @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private IrisTemplate chatTemplate; @Nullable + @JoinColumn(table = "iris_code_editor_sub_settings") @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private IrisTemplate problemStatementGenerationTemplate; @Nullable + @JoinColumn(table = "iris_code_editor_sub_settings") @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private IrisTemplate templateRepoGenerationTemplate; @Nullable + @JoinColumn(table = "iris_code_editor_sub_settings") @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private IrisTemplate solutionRepoGenerationTemplate; @Nullable + @JoinColumn(table = "iris_code_editor_sub_settings") @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private IrisTemplate testRepoGenerationTemplate; diff --git a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml index cf0e1ee28c01..ac75ca97de24 100644 --- a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml @@ -2,56 +2,83 @@ - + - + + + + + + + - + + - + - + + - + - - - + + + referencedColumnNames="id" + referencedTableName="iris_template"/> + referencedColumnNames="id" + referencedTableName="iris_template"/> - + + referencedColumnNames="id" + referencedTableName="iris_template"/> UPDATE iris_settings SET discriminator = 'GLOBAL' WHERE id IN (SELECT id FROM iris_settings WHERE is_global = TRUE); @@ -60,8 +87,55 @@ UPDATE iris_sub_settings SET discriminator = 'CHAT' WHERE id IN (SELECT iris_chat_settings_id FROM iris_settings WHERE iris_chat_settings_id IS NOT NULL); UPDATE iris_sub_settings SET discriminator = 'HESTIA' WHERE id IN (SELECT iris_hestia_settings_id FROM iris_settings WHERE iris_hestia_settings_id IS NOT NULL); + + UPDATE iris_settings SET current_version = 0 WHERE discriminator = 'GLOBAL'; + UPDATE iris_settings SET enable_auto_update_chat = FALSE WHERE discriminator = 'GLOBAL'; + UPDATE iris_settings SET enable_auto_update_hestia = FALSE WHERE discriminator = 'GLOBAL'; + UPDATE iris_settings SET enable_auto_update_code_editor = FALSE WHERE discriminator = 'GLOBAL'; + + + + + + + + + + + UPDATE iris_settings + JOIN ( + SELECT id, iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL + ) AS course_data + ON iris_settings.id = course_data.iris_settings_id + SET iris_settings.id = course_data.id; + + + UPDATE iris_settings + JOIN ( + SELECT id, iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL + ) AS exercise_data + ON iris_settings.id = exercise_data.iris_settings_id + SET iris_settings.id = exercise_data.id; + + + + + + + + + + + + UPDATE iris_settings SET course_id = course_data.id FROM (SELECT id, iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL) AS course_data WHERE iris_settings.id = course_data.iris_settings_id; + + + UPDATE iris_settings SET exercise_id = exercise_data.id FROM (SELECT id, iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL) AS exercise_data WHERE iris_settings.id = exercise_data.iris_settings_id; + + + Date: Fri, 20 Oct 2023 02:16:40 +0200 Subject: [PATCH 05/30] Client side --- .../iris/settings/IrisGlobalSettings.java | 2 +- .../iris/settings/IrisSettingsService.java | 6 +- .../iris/settings/IrisSubSettingsService.java | 4 +- .../iris/settings/iris-settings.model.ts | 45 ++++++++-- .../iris/settings/iris-sub-settings.model.ts | 30 ++++++- .../programming-exercise-detail.component.ts | 7 +- src/main/webapp/app/iris/iris.module.ts | 12 ++- .../iris-course-settings-update.component.ts | 2 +- ...is-exercise-settings-update.component.html | 4 + ...iris-exercise-settings-update.component.ts | 15 ++++ .../iris-global-settings-update.component.ts | 2 +- ...ng-exercise-settings-update.component.html | 4 - ...ming-exercise-settings-update.component.ts | 13 --- ...s-chat-sub-settings-update.component.html} | 26 ++---- ...iris-chat-sub-settings-update.component.ts | 59 +++++++++++++ ...-common-sub-settings-update.component.html | 44 ++++++++++ ...is-common-sub-settings-update.component.ts | 82 +++++++++++++++++++ ...-hestia-sub-settings-update.component.html | 11 +++ ...is-hestia-sub-settings-update.component.ts | 50 +++++++++++ .../iris-settings-update.component.html | 49 +++++++---- .../iris-settings-update.component.ts | 49 +++++++---- .../iris-sub-settings-update.component.ts | 53 ------------ .../settings/shared/iris-settings.service.ts | 46 +++++------ src/main/webapp/i18n/de/iris.json | 9 +- src/main/webapp/i18n/en/iris.json | 11 ++- 25 files changed, 461 insertions(+), 174 deletions(-) create mode 100644 src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html create mode 100644 src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.ts delete mode 100644 src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.html delete mode 100644 src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.ts rename src/main/webapp/app/iris/settings/iris-settings-update/{iris-sub-settings-update/iris-sub-settings-update.component.html => iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html} (50%) create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts delete mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.ts diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java index fa485889da86..75d2aecbb973 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java @@ -46,7 +46,7 @@ public boolean isValid() { || (irisChatSettings.getTemplate() != null && irisChatSettings.getTemplate().getContent() != null && !irisChatSettings.getTemplate().getContent().isEmpty()); var hestiaSettingsValid = !Hibernate.isInitialized(irisHestiaSettings) || irisHestiaSettings == null || (irisHestiaSettings.getTemplate() != null && irisHestiaSettings.getTemplate().getContent() != null && !irisHestiaSettings.getTemplate().getContent().isEmpty()); - return currentVersion > 0 && chatSettingsValid && hestiaSettingsValid; + return chatSettingsValid && hestiaSettingsValid; } public int getCurrentVersion() { diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 37c4179af64f..314908e9005a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -90,13 +90,13 @@ private void createInitialGlobalSettings() { */ private void autoUpdateGlobalSettings(IrisGlobalSettings settings) { if (settings.getCurrentVersion() < IrisConstants.GLOBAL_SETTINGS_VERSION) { - if (settings.isEnableAutoUpdateChat()) { + if (settings.isEnableAutoUpdateChat() || settings.getIrisChatSettings() == null) { settings.getIrisChatSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_CHAT_TEMPLATE)); } - if (settings.isEnableAutoUpdateHestia()) { + if (settings.isEnableAutoUpdateHestia() || settings.getIrisHestiaSettings() == null) { settings.getIrisHestiaSettings().setTemplate(new IrisTemplate(IrisConstants.DEFAULT_HESTIA_TEMPLATE)); } - if (settings.isEnableAutoUpdateCodeEditor()) { + if (settings.isEnableAutoUpdateCodeEditor() || settings.getIrisCodeEditorSettings() == null) { updateIrisCodeEditorSettings(settings); } settings.setCurrentVersion(IrisConstants.GLOBAL_SETTINGS_VERSION); diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index c3601d299335..73577263b7d2 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -236,7 +236,7 @@ private Integer getCombinedRateLimit(List settingsList) { private Set getCombinedAllowedModels(List settingsList, Function subSettingsFunction) { return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getAllowedModels).filter(Objects::nonNull) - .reduce((first, second) -> second).orElse(new TreeSet<>()); + .filter(models -> !models.isEmpty()).reduce((first, second) -> second).orElse(new TreeSet<>()); } /** @@ -264,6 +264,6 @@ private String getCombinedPreferredModel(List settingsList, Functi private IrisTemplate getCombinedTemplate(List settingsList, Function subSettingsFunction, Function templateFunction) { return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(templateFunction) - .filter(template -> template != null && !template.getContent().isBlank()).reduce((first, second) -> second).orElse(null); + .filter(template -> template != null && template.getContent() != null && !template.getContent().isBlank()).reduce((first, second) -> second).orElse(null); } } diff --git a/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts b/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts index 409c24ac93ac..6cd5d74b1ecd 100644 --- a/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts +++ b/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts @@ -1,9 +1,44 @@ import { BaseEntity } from 'app/shared/model/base-entity'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; -export class IrisSettings implements BaseEntity { +export enum IrisSettingsType { + GLOBAL = 'global', + COURSE = 'course', + EXERCISE = 'exercise', +} + +export abstract class IrisSettings implements BaseEntity { + id?: number; + type: IrisSettingsType; + irisChatSettings?: IrisChatSubSettings; + irisHestiaSettings?: IrisHestiaSubSettings; + irisCodeEditorSettings?: IrisCodeEditorSubSettings; +} + +export class IrisGlobalSettings implements IrisSettings { + id?: number; + type = IrisSettingsType.GLOBAL; + currentVersion?: number; + enableAutoUpdateChat?: boolean; + enableAutoUpdateHestia?: boolean; + enableAutoUpdateCodeEditor?: boolean; + irisChatSettings?: IrisChatSubSettings; + irisHestiaSettings?: IrisHestiaSubSettings; + irisCodeEditorSettings?: IrisCodeEditorSubSettings; +} + +export class IrisCourseSettings implements IrisSettings { + id?: number; + type = IrisSettingsType.COURSE; + courseId?: number; + irisChatSettings?: IrisChatSubSettings; + irisHestiaSettings?: IrisHestiaSubSettings; + irisCodeEditorSettings?: IrisCodeEditorSubSettings; +} + +export class IrisExerciseSettings implements IrisSettings { id?: number; - irisChatSettings?: IrisSubSettings; - irisHestiaSettings?: IrisSubSettings; - global = false; + type = IrisSettingsType.EXERCISE; + exerciseId?: number; + irisChatSettings?: IrisChatSubSettings; } diff --git a/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts b/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts index aa2981af0411..5000003d4952 100644 --- a/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts +++ b/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts @@ -1,11 +1,37 @@ import { BaseEntity } from 'app/shared/model/base-entity'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -export class IrisSubSettings implements BaseEntity { +export enum IrisSubSettingsType { + CHAT = 'chat', + HESTIA = 'hestia', + CODE_EDITOR = 'code-editor', +} + +export abstract class IrisSubSettings implements BaseEntity { id?: number; + type: IrisSubSettingsType; enabled = false; - template?: IrisTemplate; + allowedModels?: string[]; preferredModel?: string; +} + +export class IrisChatSubSettings extends IrisSubSettings { + type = IrisSubSettingsType.CHAT; + template?: IrisTemplate; rateLimit?: number; rateLimitTimeframeHours?: number; } + +export class IrisHestiaSubSettings extends IrisSubSettings { + type = IrisSubSettingsType.HESTIA; + template?: IrisTemplate; +} + +export class IrisCodeEditorSubSettings extends IrisSubSettings { + type = IrisSubSettingsType.CODE_EDITOR; + chatTemplate?: IrisTemplate; + problemStatementGenerationTemplate?: IrisTemplate; + templateRepoGenerationTemplate?: IrisTemplate; + solutionRepoGenerationTemplate?: IrisTemplate; + testRepoGenerationTemplate?: IrisTemplate; +} diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts index ad6707e520f0..1342f3b55d6b 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts @@ -54,7 +54,7 @@ import { DocumentationType } from 'app/shared/components/documentation-button/do import { ConsistencyCheckService } from 'app/shared/consistency-check/consistency-check.service'; import { hasEditableBuildPlan } from 'app/shared/layouts/profiles/profile-info.model'; import { PROFILE_LOCALVC } from 'app/app.constants'; -import { IrisProgrammingExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component'; +import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component'; @Component({ selector: 'jhi-programming-exercise-detail', @@ -423,8 +423,9 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { * Shows the iris settings in a modal. */ showIrisSettings(): void { - const modalRef = this.modalService.open(IrisProgrammingExerciseSettingsUpdateComponent, { size: 'xl' }); - modalRef.componentInstance.programmingExerciseId = this.programmingExercise.id; + const modalRef = this.modalService.open(IrisExerciseSettingsUpdateComponent, { size: 'xl' }); + modalRef.componentInstance.courseId = this.courseId; + modalRef.componentInstance.exerciseId = this.programmingExercise.id; } createStructuralSolutionEntries() { diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index 4bb13a252b62..015241fe175e 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -12,10 +12,12 @@ import { RouterModule } from '@angular/router'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { IrisSettingsUpdateComponent } from './settings/iris-settings-update/iris-settings-update.component'; import { IrisGlobalSettingsUpdateComponent } from './settings/iris-global-settings-update/iris-global-settings-update.component'; -import { IrisSubSettingsUpdateComponent } from './settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component'; +import { IrisCommonSubSettingsUpdateComponent } from './settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; import { IrisCourseSettingsUpdateComponent } from 'app/iris/settings/iris-course-settings-update/iris-course-settings-update.component'; -import { IrisProgrammingExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component'; +import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component'; import { IrisLogoComponent } from './iris-logo/iris-logo.component'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; @NgModule({ declarations: [ @@ -25,9 +27,11 @@ import { IrisLogoComponent } from './iris-logo/iris-logo.component'; IrisSettingsUpdateComponent, IrisGlobalSettingsUpdateComponent, IrisCourseSettingsUpdateComponent, - IrisProgrammingExerciseSettingsUpdateComponent, - IrisSubSettingsUpdateComponent, + IrisExerciseSettingsUpdateComponent, + IrisCommonSubSettingsUpdateComponent, IrisLogoComponent, + IrisChatSubSettingsUpdateComponent, + IrisHestiaSubSettingsUpdateComponent, ], imports: [CommonModule, MatDialogModule, FormsModule, FontAwesomeModule, ArtemisSharedModule, ArtemisMarkdownModule, ArtemisSharedComponentModule, RouterModule], providers: [], diff --git a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.ts index 6a38055b6253..2c2eb6701f56 100644 --- a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { IrisSettingsType } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; @Component({ selector: 'jhi-iris-course-settings-update', diff --git a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html new file mode 100644 index 000000000000..e8f9278177a5 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html @@ -0,0 +1,4 @@ +
+

Programming Exercise Iris Settings

+ +
diff --git a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.ts new file mode 100644 index 000000000000..7edf9cd15547 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; +import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; + +@Component({ + selector: 'jhi-iris-exercise-settings-update', + templateUrl: './iris-exercise-settings-update.component.html', +}) +export class IrisExerciseSettingsUpdateComponent { + @Input() + public courseId?: number; + @Input() + public exerciseId?: number; + + EXERCISE = IrisSettingsType.EXERCISE; +} diff --git a/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.ts index 5f6d1091a6e7..b48a5d390ac4 100644 --- a/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { IrisSettingsType } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; @Component({ selector: 'jhi-iris-global-settings-update', diff --git a/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.html deleted file mode 100644 index e1c03e8ab918..000000000000 --- a/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

Programming Exercise Iris Settings

- -
diff --git a/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.ts deleted file mode 100644 index 7d0b4daf08f1..000000000000 --- a/src/main/webapp/app/iris/settings/iris-programming-exercise-settings-update/iris-programming-exercise-settings-update.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { IrisSettingsType } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; - -@Component({ - selector: 'jhi-iris-programming-exercise-settings-update', - templateUrl: './iris-programming-exercise-settings-update.component.html', -}) -export class IrisProgrammingExerciseSettingsUpdateComponent { - @Input() - public programmingExerciseId?: number; - - PROGRAMMING_EXERCISE = IrisSettingsType.PROGRAMMING_EXERCISE; -} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html similarity index 50% rename from src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.html rename to src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html index ca9a800ee168..8b3dacc84745 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html @@ -1,22 +1,3 @@ -
- - -
-Preferred Model: -
- -
- - -
-
-
@@ -40,8 +21,11 @@
- +
- + +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts new file mode 100644 index 000000000000..01e6d912d4f0 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts @@ -0,0 +1,59 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisChatSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; + +@Component({ + selector: 'jhi-iris-chat-sub-settings-update', + templateUrl: './iris-chat-sub-settings-update.component.html', +}) +export class IrisChatSubSettingsUpdateComponent implements OnInit, OnChanges { + @Input() + subSettings?: IrisChatSubSettings; + + @Input() + parentSubSettings?: IrisChatSubSettings; + + @Input() + templateOptional = false; + + @Input() + rateLimitSettable = false; + + previousTemplate?: IrisTemplate; + + isAdmin: boolean; + + templateContent: string; + + ngOnInit(): void { + this.templateContent = this.subSettings?.template?.content ?? this.parentSubSettings?.template?.content ?? ''; + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.subSettings || changes.parentSubSettings) { + this.templateContent = this.subSettings?.template?.content ?? this.parentSubSettings?.template?.content ?? ''; + } + } + + onInheritTemplateChanged() { + if (this.subSettings?.template) { + this.previousTemplate = this.subSettings?.template; + this.subSettings.template = undefined; + this.templateContent = this.parentSubSettings?.template?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.template = this.previousTemplate ?? irisTemplate; + } + } + + onTemplateChanged() { + if (this.subSettings?.template) { + this.subSettings.template.content = this.templateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.templateContent; + this.subSettings!.template = irisTemplate; + } + } +} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html new file mode 100644 index 000000000000..2e811bdf2b77 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html @@ -0,0 +1,44 @@ +
+ + +
+ +Allowed Models: + +
+ +
+ +
+ +Preferred Model: +
+ +
+ + +
+
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts new file mode 100644 index 000000000000..6f318239e2a5 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -0,0 +1,82 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { AccountService } from 'app/core/auth/account.service'; +import { ButtonType } from 'app/shared/components/button.component'; +import { faTrash } from '@fortawesome/free-solid-svg-icons'; + +@Component({ + selector: 'jhi-iris-common-sub-settings-update', + templateUrl: './iris-common-sub-settings-update.component.html', +}) +export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { + @Input() + subSettings?: IrisSubSettings; + + @Input() + parentSubSettings?: IrisSubSettings; + + @Input() + allIrisModels: IrisModel[]; + + @Input() + modelOptional = false; + + isAdmin: boolean; + + allowedIrisModelsInherited: boolean; + + allowedIrisModels: IrisModel[]; + + enabled: boolean; + + // Button types + WARNING = ButtonType.WARNING; + // Icons + faTrash = faTrash; + + constructor(accountService: AccountService) { + this.isAdmin = accountService.isAdmin(); + } + + ngOnInit() { + this.enabled = this.subSettings?.enabled ?? false; + this.allowedIrisModels = this.getAvailableModels(); + this.allowedIrisModelsInherited = !this.subSettings?.allowedModels && this.parentSubSettings !== undefined; + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.allIrisModels) { + this.allowedIrisModels = this.getAvailableModels(); + } + if (changes.subSettings) { + this.enabled = this.subSettings?.enabled ?? false; + } + } + + getAvailableModels(): IrisModel[] { + return this.allIrisModels.filter((model) => (this.subSettings?.allowedModels ?? this.parentSubSettings?.allowedModels ?? []).includes(model.id)); + } + + getSelectedModelName(): string { + return this.allIrisModels.find((model) => model.id === this.subSettings?.preferredModel)?.name ?? this.subSettings?.preferredModel ?? 'Inherit'; + } + + onAllowedIrisModelsSelectionChange() { + this.allowedIrisModelsInherited = false; + this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); + } + + setModel(model: IrisModel | undefined) { + this.subSettings!.preferredModel = model?.id; + } + + inheritAllowedModels() { + this.allowedIrisModelsInherited = true; + this.subSettings!.allowedModels = undefined; + } + + onEnabledChange() { + this.subSettings!.enabled = this.enabled; + } +} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html new file mode 100644 index 000000000000..00890c975fc5 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html @@ -0,0 +1,11 @@ +
+ +
+ + +
+ + +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts new file mode 100644 index 000000000000..c294ed057807 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts @@ -0,0 +1,50 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; + +@Component({ + selector: 'jhi-iris-hestia-sub-settings-update', + templateUrl: './iris-hestia-sub-settings-update.component.html', +}) +export class IrisHestiaSubSettingsUpdateComponent implements OnInit { + @Input() + subSettings: IrisHestiaSubSettings; + + @Input() + parentSubSettings?: IrisHestiaSubSettings; + + @Input() + templateOptional = false; + + previousTemplate?: IrisTemplate; + + isAdmin: boolean; + + templateContent: string; + + ngOnInit(): void { + this.templateContent = this.subSettings.template?.content ?? this.parentSubSettings?.template?.content ?? ''; + } + + onInheritTemplateChanged() { + if (this.subSettings.template) { + this.previousTemplate = this.subSettings.template; + this.subSettings.template = undefined; + this.templateContent = this.parentSubSettings?.template?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings.template = this.previousTemplate ?? irisTemplate; + } + } + + onTemplateChanged() { + if (this.subSettings.template) { + this.subSettings.template.content = this.templateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.templateContent; + this.subSettings.template = irisTemplate; + } + } +} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html index c019b93e5ab9..8ec959e544a3 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html @@ -6,27 +6,44 @@

Chat Settings

- + + >
-
-

Hestia Settings

-
- - -
-
- +
+

Hestia Settings

+ + +
+ +
+
+

Code Editor Settings

+ + >
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 115e054e830a..55c33df74f6f 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -1,20 +1,14 @@ import { Component, Input, OnInit } from '@angular/core'; -import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisSettings, IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; import { HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { ButtonType } from 'app/shared/components/button.component'; import { faRotate, faSave } from '@fortawesome/free-solid-svg-icons'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; -export enum IrisSettingsType { - GLOBAL = 'GLOBAL', - COURSE = 'COURSE', - PROGRAMMING_EXERCISE = 'PROGRAMMING_EXERCISE', -} - @Component({ selector: 'jhi-iris-settings-update', templateUrl: './iris-settings-update.component.html', @@ -25,10 +19,11 @@ export class IrisSettingsUpdateComponent implements OnInit { @Input() public courseId?: number; @Input() - public programmingExerciseId?: number; + public exerciseId?: number; public irisSettings?: IrisSettings; - public irisModels?: IrisModel[]; + public parentIrisSettings?: IrisSettings; + public allIrisModels?: IrisModel[]; // Loading bools isLoading = false; @@ -41,7 +36,8 @@ export class IrisSettingsUpdateComponent implements OnInit { faRotate = faRotate; // Settings types GLOBAL = IrisSettingsType.GLOBAL; - PROGRAMMING_EXERCISE = IrisSettingsType.PROGRAMMING_EXERCISE; + COURSE = IrisSettingsType.COURSE; + EXERCISE = IrisSettingsType.EXERCISE; constructor( private irisSettingsService: IrisSettingsService, @@ -54,7 +50,7 @@ export class IrisSettingsUpdateComponent implements OnInit { loadIrisModels(): void { this.irisSettingsService.getIrisModels().subscribe((models) => { - this.irisModels = models; + this.allIrisModels = models; this.isLoading = false; }); } @@ -67,6 +63,13 @@ export class IrisSettingsUpdateComponent implements OnInit { this.alertService.error('artemisApp.iris.settings.error.noSettings'); } this.irisSettings = settings; + console.log(this.irisSettings); + }); + this.loadParentIrisSettingsObservable().subscribe((settings) => { + if (!settings) { + this.alertService.error('artemisApp.iris.settings.error.noParentSettings'); + } + this.parentIrisSettings = settings; }); } @@ -85,14 +88,26 @@ export class IrisSettingsUpdateComponent implements OnInit { ); } + loadParentIrisSettingsObservable(): Observable { + switch (this.settingType) { + case IrisSettingsType.GLOBAL: + // Global settings have no parent + return new Observable(); + case IrisSettingsType.COURSE: + return this.irisSettingsService.getGlobalSettings(); + case IrisSettingsType.EXERCISE: + return this.irisSettingsService.getCombinedCourseSettings(this.courseId!); + } + } + loadIrisSettingsObservable(): Observable { switch (this.settingType) { case IrisSettingsType.GLOBAL: return this.irisSettingsService.getGlobalSettings(); case IrisSettingsType.COURSE: return this.irisSettingsService.getUncombinedCourseSettings(this.courseId!); - case IrisSettingsType.PROGRAMMING_EXERCISE: - return this.irisSettingsService.getUncombinedProgrammingExerciseSettings(this.programmingExerciseId!); + case IrisSettingsType.EXERCISE: + return this.irisSettingsService.getUncombinedProgrammingExerciseSettings(this.exerciseId!); } } @@ -102,8 +117,8 @@ export class IrisSettingsUpdateComponent implements OnInit { return this.irisSettingsService.setGlobalSettings(this.irisSettings!); case IrisSettingsType.COURSE: return this.irisSettingsService.setCourseSettings(this.courseId!, this.irisSettings!); - case IrisSettingsType.PROGRAMMING_EXERCISE: - return this.irisSettingsService.setProgrammingExerciseSettings(this.programmingExerciseId!, this.irisSettings!); + case IrisSettingsType.EXERCISE: + return this.irisSettingsService.setProgrammingExerciseSettings(this.exerciseId!, this.irisSettings!); } } @@ -111,7 +126,7 @@ export class IrisSettingsUpdateComponent implements OnInit { if (this.irisSettings?.irisHestiaSettings) { this.irisSettings!.irisHestiaSettings = undefined; } else { - const irisSubSettings = new IrisSubSettings(); + const irisSubSettings = new IrisHestiaSubSettings(); irisSubSettings.enabled = true; this.irisSettings!.irisHestiaSettings = irisSubSettings; } diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.ts deleted file mode 100644 index dde988881ba9..000000000000 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; -import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; -import { AccountService } from 'app/core/auth/account.service'; - -@Component({ - selector: 'jhi-iris-sub-settings-update', - templateUrl: './iris-sub-settings-update.component.html', -}) -export class IrisSubSettingsUpdateComponent { - @Input() - subSettings: IrisSubSettings; - - @Input() - models: IrisModel[]; - - @Input() - modelOptional = false; - - @Input() - templateOptional = false; - - @Input() - rateLimitSettable = false; - - previousTemplate?: IrisTemplate; - - isAdmin: boolean; - - constructor(accountService: AccountService) { - this.isAdmin = accountService.isAdmin(); - } - - onInheritTemplateChanged() { - if (this.subSettings.template) { - this.previousTemplate = this.subSettings.template; - this.subSettings.template = undefined; - } else { - const irisTemplate = new IrisTemplate(); - irisTemplate.content = ''; - this.subSettings.template = this.previousTemplate ?? irisTemplate; - } - } - - getSelectedModelName(): string { - return this.models.find((model) => model.id === this.subSettings.preferredModel)?.name ?? this.subSettings.preferredModel ?? 'None'; - } - - setModel(model: IrisModel | undefined) { - this.subSettings.preferredModel = model?.id; - } -} diff --git a/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts b/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts index 40c775b2e5f5..475540bad156 100644 --- a/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts +++ b/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts @@ -2,11 +2,9 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisCourseSettings, IrisExerciseSettings, IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; -type EntityResponseType = HttpResponse; - /** * Service for calling the Iris settings endpoints on the server */ @@ -19,58 +17,58 @@ export class IrisSettingsService { /** * Get the global Iris settings */ - getGlobalSettings(): Observable { + getGlobalSettings(): Observable { return this.http - .get(`${this.resourceUrl}/iris/global-iris-settings`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? undefined)); + .get(`${this.resourceUrl}/iris/global-iris-settings`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? undefined)); } /** * Get the uncombined Iris settings for a course * @param courseId the id of the course */ - getUncombinedCourseSettings(courseId: number): Observable { + getUncombinedCourseSettings(courseId: number): Observable { return this.http - .get(`${this.resourceUrl}/courses/${courseId}/raw-iris-settings`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? undefined)); + .get(`${this.resourceUrl}/courses/${courseId}/raw-iris-settings`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? undefined)); } /** * Get the combined Iris settings for a course * @param courseId the id of the course */ - getCombinedCourseSettings(courseId: number): Observable { + getCombinedCourseSettings(courseId: number): Observable { return this.http - .get(`${this.resourceUrl}/courses/${courseId}/iris-settings`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? undefined)); + .get(`${this.resourceUrl}/courses/${courseId}/iris-settings`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? undefined)); } /** * Get the uncombined Iris settings for a programming exercise * @param exerciseId the id of the programming exercise */ - getUncombinedProgrammingExerciseSettings(exerciseId: number): Observable { + getUncombinedProgrammingExerciseSettings(exerciseId: number): Observable { return this.http - .get(`${this.resourceUrl}/programming-exercises/${exerciseId}/raw-iris-settings`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? undefined)); + .get(`${this.resourceUrl}/programming-exercises/${exerciseId}/raw-iris-settings`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? undefined)); } /** * Get the combined Iris settings for a programming exercise * @param exerciseId the id of the programming exercise */ - getCombinedProgrammingExerciseSettings(exerciseId: number): Observable { + getCombinedProgrammingExerciseSettings(exerciseId: number): Observable { return this.http - .get(`${this.resourceUrl}/programming-exercises/${exerciseId}/iris-settings`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? undefined)); + .get(`${this.resourceUrl}/programming-exercises/${exerciseId}/iris-settings`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? undefined)); } /** * Update the global Iris settings * @param settings the settings to set */ - setGlobalSettings(settings: IrisSettings): Observable { - return this.http.put(`${this.resourceUrl}/admin/iris/global-iris-settings`, settings, { observe: 'response' }); + setGlobalSettings(settings: IrisGlobalSettings): Observable> { + return this.http.put(`${this.resourceUrl}/admin/iris/global-iris-settings`, settings, { observe: 'response' }); } /** @@ -78,8 +76,8 @@ export class IrisSettingsService { * @param courseId the id of the course * @param settings the settings to set */ - setCourseSettings(courseId: number, settings: IrisSettings): Observable { - return this.http.put(`${this.resourceUrl}/courses/${courseId}/raw-iris-settings`, settings, { observe: 'response' }); + setCourseSettings(courseId: number, settings: IrisCourseSettings): Observable> { + return this.http.put(`${this.resourceUrl}/courses/${courseId}/raw-iris-settings`, settings, { observe: 'response' }); } /** @@ -87,8 +85,8 @@ export class IrisSettingsService { * @param exerciseId the id of the programming exercise * @param settings the settings to set */ - setProgrammingExerciseSettings(exerciseId: number, settings: IrisSettings): Observable { - return this.http.put(`${this.resourceUrl}/programming-exercises/${exerciseId}/raw-iris-settings`, settings, { observe: 'response' }); + setProgrammingExerciseSettings(exerciseId: number, settings: IrisExerciseSettings): Observable> { + return this.http.put(`${this.resourceUrl}/programming-exercises/${exerciseId}/raw-iris-settings`, settings, { observe: 'response' }); } /** diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index 19c996e4af7c..25e456e7975d 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -22,16 +22,23 @@ "subSettings": { "chatSettings": "Chat Einstellungen", "hestiaSettings": "Hestia Einstellungen", + "codeEditorSettings": "Code Editor Einstellungen", "inheritChatSettings": "Vererbe Chat Einstellungen", "inheritHestiaSettings": "Vererbe Hestia Einstellungen", "enabled-disabled": "Aktiviert/Deaktiviert", + "allowedModels": { + "title": "Erlaubte Modelle", + "inheritedInfo": "Diese Modelle sind von einem höherem Level geerbt. Ändere sie hier, um sie in diesem Level zu überschreiben.", + "inheritButton": "Erlaubte Modelle vererben" + }, "preferredModel": "Präferiertes Modell", "inheritModel": "Vererbe Modell", "rateLimit": "Rate Limit", "rateLimitTooltip": "Die maximale Anzahl an Antworten, die ein Benutzer vom LLM in einem bestimmten Zeitraum erhalten kann.", "template": { "title": "Template", - "inherit": "Inherit Template" + "inherit": "Template erben", + "inheritedInfo": "Dieses Template ist von einem höherem Level geerbt. Ändere es hier, um es in diesem Level zu überschreiben." } }, "title": { diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index 9b7f9cdced0c..8eb5d01dcfed 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -22,9 +22,13 @@ "subSettings": { "chatSettings": "Chat Settings", "hestiaSettings": "Hestia Settings", - "inheritChatSettings": "Inherit Chat Settings", - "inheritHestiaSettings": "Inherit Hestia Settings", + "codeEditorSettings": "Code Editor Settings", "enabled-disabled": "Enabled/Disabled", + "allowedModels": { + "title": "Allowed Models", + "inheritedInfo": "These models are inherited from higher level settings. Edit them here to overwrite them for this level.", + "inheritButton": "Inherit Allowed Models" + }, "preferredModel": "Preferred Model", "inheritModel": "Inherit Model", "rateLimit": "Rate Limit", @@ -33,7 +37,8 @@ "rateLimitTimeframeHoursTooltip": "The time period in which the rate limit applies in hours.", "template": { "title": "Template", - "inherit": "Inherit Template" + "inherit": "Inherit Template", + "inheritedInfo": "This template is inherited from higher level settings. Edit it here to overwrite it for this level." } }, "title": { From b7a02ba544c433bd9d0146bf00cc65e642598f07 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Fri, 20 Oct 2023 02:21:12 +0200 Subject: [PATCH 06/30] Small fixes --- .../artemis/web/rest/admin/iris/AdminIrisSettingsResource.java | 2 ++ .../config/liquibase/changelog/20231019191919_changelog.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java index 0eba50c5b866..dacf5c65b3fb 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/iris/AdminIrisSettingsResource.java @@ -1,5 +1,6 @@ package de.tum.in.www1.artemis.web.rest.admin.iris; +import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -13,6 +14,7 @@ /** * REST controller for managing {@link IrisSettings}. */ +@Profile("iris") @RestController @RequestMapping("api/admin/") public class AdminIrisSettingsResource { diff --git a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml index ac75ca97de24..d8d93dec023f 100644 --- a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml @@ -81,7 +81,7 @@ referencedTableName="iris_template"/> - UPDATE iris_settings SET discriminator = 'GLOBAL' WHERE id IN (SELECT id FROM iris_settings WHERE is_global = TRUE); + UPDATE iris_settings SET discriminator = 'GLOBAL' WHERE is_global = TRUE; UPDATE iris_settings SET discriminator = 'COURSE' WHERE id IN (SELECT iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL); UPDATE iris_settings SET discriminator = 'EXERCISE' WHERE id IN (SELECT iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL); From f8255082a7013bc63286a30a53ae61cd994ed862 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sat, 21 Oct 2023 00:46:52 +0200 Subject: [PATCH 07/30] Fix CodeHintResource --- .../artemis/web/rest/hestia/CodeHintResource.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java index b2339f5c4ca5..db69ff5120ef 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/hestia/CodeHintResource.java @@ -1,11 +1,10 @@ package de.tum.in.www1.artemis.web.rest.hestia; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -42,11 +41,11 @@ public class CodeHintResource { private final CodeHintService codeHintService; - private final IrisSettingsService irisSettingsService; + private final Optional irisSettingsService; public CodeHintResource(AuthorizationCheckService authCheckService, ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, CodeHintRepository codeHintRepository, CodeHintService codeHintService, - IrisSettingsService irisSettingsService) { + Optional irisSettingsService) { this.programmingExerciseRepository = programmingExerciseRepository; this.authCheckService = authCheckService; this.solutionEntryRepository = solutionEntryRepository; @@ -103,6 +102,7 @@ public ResponseEntity> generateCodeHintsForExercise(@PathVariable * @param codeHintId The id of the code hint * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the updated code hint */ + @Profile("iris") @PostMapping("programming-exercises/{exerciseId}/code-hints/{codeHintId}/generate-description") @EnforceAtLeastEditor public ResponseEntity generateDescriptionForCodeHint(@PathVariable Long exerciseId, @PathVariable Long codeHintId) { @@ -110,7 +110,7 @@ public ResponseEntity generateDescriptionForCodeHint(@PathVariable Lon ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.HESTIA, exercise); + irisSettingsService.orElseThrow().isEnabledForElseThrow(IrisSubSettingsType.HESTIA, exercise); // Hints for exam exercises are not supported at the moment if (exercise.isExamExercise()) { From 0792f0939ccd59f7d488c573953cb48c85bc28cf Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Mon, 23 Oct 2023 12:30:29 +0200 Subject: [PATCH 08/30] Fix IrisSettingsResource --- .../tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java index cdc51d6190f6..b65efaef8694 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java @@ -1,5 +1,6 @@ package de.tum.in.www1.artemis.web.rest.iris; +import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -18,6 +19,7 @@ /** * REST controller for managing {@link IrisSettings}. */ +@Profile("iris") @RestController @RequestMapping("api/") public class IrisSettingsResource { From 6ec2983fd68c00179b85d1841c7fbed3192bb998 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Mon, 23 Oct 2023 12:58:42 +0200 Subject: [PATCH 09/30] Fix server tests --- .../de/tum/in/www1/artemis/repository/CourseRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java index 0c1e932ea3a4..9226234c4182 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java @@ -143,7 +143,7 @@ SELECT CASE WHEN (count(c) > 0) THEN true ELSE false END @EntityGraph(type = LOAD, attributePaths = { "lectures", "lectures.lectureUnits" }) Optional findWithEagerLecturesAndLectureUnitsById(long courseId); - @EntityGraph(type = LOAD, attributePaths = { "organizations", "competencies", "prerequisites", "tutorialGroupsConfiguration", "onlineCourseConfiguration", "irisSettings" }) + @EntityGraph(type = LOAD, attributePaths = { "organizations", "competencies", "prerequisites", "tutorialGroupsConfiguration", "onlineCourseConfiguration" }) Optional findForUpdateById(long courseId); @EntityGraph(type = LOAD, attributePaths = { "exercises", "lectures", "lectures.lectureUnits", "competencies", "prerequisites" }) From 443c6dea0013e6a9941f15f4cdf08ce12e153bb9 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Wed, 25 Oct 2023 16:23:49 +0200 Subject: [PATCH 10/30] Fix migration --- .../changelog/20231019191919_changelog.xml | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml index d8d93dec023f..5eae63266554 100644 --- a/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20231019191919_changelog.xml @@ -22,8 +22,12 @@ - - + + + + + + @@ -105,25 +109,25 @@ UPDATE iris_settings JOIN ( - SELECT id, iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL + SELECT id, iris_settings_id FROM course WHERE iris_settings_id IS NOT NULL ) AS course_data ON iris_settings.id = course_data.iris_settings_id - SET iris_settings.id = course_data.id; + SET iris_settings.course_id = course_data.id; UPDATE iris_settings JOIN ( - SELECT id, iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL + SELECT id, iris_settings_id FROM programming_exercise_details WHERE iris_settings_id IS NOT NULL ) AS exercise_data ON iris_settings.id = exercise_data.iris_settings_id - SET iris_settings.id = exercise_data.id; + SET iris_settings.exercise_id = exercise_data.id; - + @@ -137,11 +141,15 @@ - + - - - + + + From 86ed8cfe0ff5791e7efbaabb6343fc781b7d35db Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Wed, 25 Oct 2023 16:49:18 +0200 Subject: [PATCH 11/30] Add JavaDocs --- .../iris/settings/IrisChatSubSettings.java | 5 +- .../settings/IrisCodeEditorSubSettings.java | 4 +- .../iris/settings/IrisCourseSettings.java | 7 +- .../iris/settings/IrisExerciseSettings.java | 5 +- .../iris/settings/IrisGlobalSettings.java | 6 +- .../iris/settings/IrisHestiaSubSettings.java | 4 +- .../domain/iris/settings/IrisSettings.java | 10 +- .../domain/iris/settings/IrisSubSettings.java | 9 +- .../iris/settings/IrisSettingsService.java | 98 +++++++++++++++++-- .../iris/settings/IrisSubSettingsService.java | 45 ++++++++- 10 files changed, 164 insertions(+), 29 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java index ac94b548b586..777e65cad7ce 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisChatSubSettings.java @@ -8,8 +8,9 @@ import de.tum.in.www1.artemis.domain.iris.IrisTemplate; /** - * An IrisSubSettings object represents the settings for a specific feature of Iris. - * {@link IrisSettings} is the parent of this class. + * An {@link IrisSubSettings} implementation for chat settings. + * Chat settings notably provide settings for the rate limit. + * Chat settings provide a single {@link IrisTemplate} for the chat messages. */ @Entity @DiscriminatorValue("CHAT") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java index ee2dab8ef7f4..fe17cb0e8537 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCodeEditorSubSettings.java @@ -8,8 +8,8 @@ import de.tum.in.www1.artemis.domain.iris.IrisTemplate; /** - * An IrisSubSettings object represents the settings for a specific feature of Iris. - * {@link IrisSettings} is the parent of this class. + * An {@link IrisSubSettings} implementation for code editor settings. + * Code editor settings notably provide multiple {@link IrisTemplate}s for the different steps in the code generation. */ @Entity @DiscriminatorValue("CODE_EDITOR") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java index fc59ae92938c..4266bece8c29 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisCourseSettings.java @@ -7,9 +7,8 @@ import de.tum.in.www1.artemis.domain.Course; /** - * An IrisSettings object represents the settings for Iris for a part of Artemis. - * These settings can be either global, course or exercise specific. - * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + * An {@link IrisSettings} implementation for course specific settings. + * Course settings are used to override global settings and allows all sub setting types. */ @Entity @DiscriminatorValue("COURSE") @@ -28,7 +27,7 @@ public class IrisCourseSettings extends IrisSettings { @JoinColumn(name = "iris_hestia_settings_id") private IrisHestiaSubSettings irisHestiaSettings; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "iris_code_editor_settings_id") private IrisCodeEditorSubSettings irisCodeEditorSettings; diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java index 29460b225942..b5d0a0fb218d 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisExerciseSettings.java @@ -7,9 +7,8 @@ import de.tum.in.www1.artemis.domain.Exercise; /** - * An IrisSettings object represents the settings for Iris for a part of Artemis. - * These settings can be either global, course or exercise specific. - * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + * An {@link IrisSettings} implementation for exercise specific settings. + * Exercise settings are used to override course settings and currently only allow setting the {@link IrisChatSubSettings}. */ @Entity @DiscriminatorValue("EXERCISE") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java index 75d2aecbb973..66995a7410b9 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisGlobalSettings.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; /** - * An IrisSettings object represents the settings for Iris for a part of Artemis. - * These settings can be either global, course or exercise specific. - * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + * An {@link IrisSettings} implementation for global settings. + * Global settings provide default values for all of Artemis for all sub setting types. + * It also includes functionality to automatically update the sub settings in the future. */ @Entity @DiscriminatorValue("GLOBAL") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java index 66e5d0604a40..4657d3796c79 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisHestiaSubSettings.java @@ -8,8 +8,8 @@ import de.tum.in.www1.artemis.domain.iris.IrisTemplate; /** - * An IrisSubSettings object represents the settings for a specific feature of Iris. - * {@link IrisSettings} is the parent of this class. + * An {@link IrisSubSettings} implementation for the Hestia integration settings. + * Hestia settings provide a single {@link IrisTemplate} for the hestia code hint generation requests. */ @Entity @DiscriminatorValue("HESTIA") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java index b38dae4c9cb6..5ca32f9b18c5 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSettings.java @@ -12,9 +12,13 @@ import de.tum.in.www1.artemis.domain.DomainObject; /** - * An IrisSettings object represents the settings for Iris for a part of Artemis. - * These settings can be either global, course or exercise specific. - * {@link de.tum.in.www1.artemis.service.iris.IrisSettingsService} for more details how IrisSettings are used. + * IrisSettings is an abstract super class for the specific settings types. + * Settings bundle {@link IrisSubSettings} together. + * {@link IrisGlobalSettings} are used to specify settings on a global level. + * {@link IrisCourseSettings} are used to specify settings on a course level. + * {@link IrisExerciseSettings} are used to specify settings on an exercise level. + *

+ * Also see {@link de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService} for more information. */ @Entity @Table(name = "iris_settings") diff --git a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java index ad7547adef9b..e9cc586a444d 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/iris/settings/IrisSubSettings.java @@ -16,8 +16,13 @@ import de.tum.in.www1.artemis.domain.DomainObject; /** - * An IrisSubSettings object represents the settings for a specific feature of Iris. - * {@link IrisSettings} is the parent of this class. + * IrisSubSettings is an abstract super class for the specific sub settings types. + * Sub Settings are settings for a specific feature of Iris. + * {@link IrisChatSubSettings} are used to specify settings for the chat feature. + * {@link IrisHestiaSubSettings} are used to specify settings for the Hestia integration. + * {@link IrisCodeEditorSubSettings} are used to specify settings for the code editor feature. + *

+ * Also see {@link de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService} for more information. */ @Entity @Table(name = "iris_sub_settings") diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 314908e9005a..5422b1eb7e8a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -25,6 +25,7 @@ * This service is responsible for CRUD operations on {@link IrisSettings}. * It also provides methods for combining multiple {@link IrisSettings} and checking if a certain Iris feature is * enabled for an exercise. + * See {@link IrisSubSettingsService} for more information on the handling of {@link IrisSubSettings}. */ @Service @Profile("iris") @@ -42,8 +43,9 @@ public IrisSettingsService(IrisSettingsRepository irisSettingsRepository, IrisSu /** * Hooks into the {@link ApplicationReadyEvent} and creates or updates the global IrisSettings object on startup. * - * @param event Specifies when this method gets called and provides the event with all application data + * @param event Unused event param used to specify when the method should be executed */ + @Profile("scheduling") @EventListener public void execute(ApplicationReadyEvent event) throws Exception { var allGlobalSettings = irisSettingsRepository.findAllGlobalSettings(); @@ -139,11 +141,11 @@ public T saveIrisSettings(T settings) { } /** - * Save the Iris settings. Should always be used over directly calling the repository. - * Ensures that there is only one global Iris settings object. + * Save a new IrisSettings object. Should always be used over directly calling the repository. + * Ensures that the settings are valid and that no settings for the given object already exist. * - * @param settings The Iris settings to save - * @return The saved Iris settings + * @param settings The IrisSettings to save + * @return The saved IrisSettings */ private T saveNewIrisSettings(T settings) { if (settings instanceof IrisGlobalSettings) { @@ -161,8 +163,18 @@ private T saveNewIrisSettings(T settings) { return irisSettingsRepository.save(settings); } + /** + * Update an existing IrisSettings object. Should always be used over directly calling the repository. + * Ensures that the settings are valid and that the existing settings ID matches the update ID. + * Then updates the existing settings according to the type of the settings object. + * + * @param The subtype of the IrisSettings object + * @param existingSettingsId The ID of the existing IrisSettings object + * @param settingsUpdate The Iris settings object to update + * @return The updated IrisSettings + */ @SuppressWarnings("unchecked") - public T updateIrisSettings(long existingSettingsId, T settingsUpdate) { + private T updateIrisSettings(long existingSettingsId, T settingsUpdate) { if (!Objects.equals(existingSettingsId, settingsUpdate.getId())) { throw new ConflictException("Existing Iris settings ID does not match update ID", "IrisSettings", "idMismatch"); } @@ -186,6 +198,13 @@ else if (existingSettings instanceof IrisExerciseSettings exerciseSettings && se } } + /** + * Helper method to update global Iris settings. + * + * @param existingSettings The existing global Iris settings + * @param settingsUpdate The global Iris settings to update + * @return The updated global Iris settings + */ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSettings, IrisGlobalSettings settingsUpdate) { existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null)); existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null)); @@ -193,6 +212,13 @@ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSetti return irisSettingsRepository.save(existingSettings); } + /** + * Helper method to update course Iris settings. + * + * @param existingSettings The existing course Iris settings + * @param settingsUpdate The course Iris settings to update + * @return The updated course Iris settings + */ private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSettings, IrisCourseSettings settingsUpdate) { var parentSettings = getCombinedIrisGlobalSettings(); existingSettings.setIrisChatSettings( @@ -204,6 +230,13 @@ private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSetti return irisSettingsRepository.save(existingSettings); } + /** + * Helper method to update exercise Iris settings. + * + * @param existingSettings The existing exercise Iris settings + * @param settingsUpdate The exercise Iris settings to update + * @return The updated exercise Iris settings + */ private IrisExerciseSettings updateExerciseSettings(IrisExerciseSettings existingSettings, IrisExerciseSettings settingsUpdate) { var parentSettings = getCombinedIrisSettingsFor(existingSettings.getExercise().getCourseViaExerciseGroupOrCourseMember(), false); existingSettings.setIrisChatSettings( @@ -241,6 +274,11 @@ public void isEnabledForElseThrow(IrisSubSettingsType type, Exercise exercise) { } } + /** + * Get the global Iris settings as an {@link IrisCombinedSettingsDTO}. + * + * @return The (combined) global Iris settings + */ public IrisCombinedSettingsDTO getCombinedIrisGlobalSettings() { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); @@ -249,6 +287,16 @@ public IrisCombinedSettingsDTO getCombinedIrisGlobalSettings() { irisSubSettingsService.combineCodeEditorSettings(settingsList, false)); } + /** + * Get the combined Iris settings for a course as an {@link IrisCombinedSettingsDTO}. + * Combines the global Iris settings with the course Iris settings. + * If minimal is true, only certain attributes are returned. The minimal version can safely be passed to the students. + * See also {@link IrisSubSettingsService} for how the combining works in detail + * + * @param course The course to get the Iris settings for + * @param minimal Whether to return the minimal version of the settings + * @return The combined Iris settings for the course + */ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Course course, boolean minimal) { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); @@ -258,6 +306,16 @@ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Course course, boolean irisSubSettingsService.combineCodeEditorSettings(settingsList, minimal)); } + /** + * Get the combined Iris settings for an exercise as an {@link IrisCombinedSettingsDTO}. + * Combines the global Iris settings with the course Iris settings and the exercise Iris settings. + * If minimal is true, only certain attributes are returned. The minimal version can safely be passed to the students. + * See also {@link IrisSubSettingsService} for how the combining works in detail + * + * @param exercise The exercise to get the Iris settings for + * @param minimal Whether to return the minimal version of the settings + * @return The combined Iris settings for the exercise + */ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Exercise exercise, boolean minimal) { var settingsList = new ArrayList(); settingsList.add(getGlobalSettings()); @@ -268,6 +326,13 @@ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Exercise exercise, boo irisSubSettingsService.combineCodeEditorSettings(settingsList, minimal)); } + /** + * Get the default Iris settings for a course. + * The default settings are used if no Iris settings for the course exist. + * + * @param course The course to get the default Iris settings for + * @return The default Iris settings for the course + */ public IrisCourseSettings getDefaultSettingsFor(Course course) { var settings = new IrisCourseSettings(); settings.setCourse(course); @@ -277,6 +342,13 @@ public IrisCourseSettings getDefaultSettingsFor(Course course) { return settings; } + /** + * Get the default Iris settings for an exercise. + * The default settings are used if no Iris settings for the exercise exist. + * + * @param exercise The exercise to get the default Iris settings for + * @return The default Iris settings for the exercise + */ public IrisExerciseSettings getDefaultSettingsFor(Exercise exercise) { var settings = new IrisExerciseSettings(); settings.setExercise(exercise); @@ -284,10 +356,24 @@ public IrisExerciseSettings getDefaultSettingsFor(Exercise exercise) { return settings; } + /** + * Get the raw (uncombined) Iris settings for a course. + * If no Iris settings for the course exist, the default settings are returned. + * + * @param course The course to get the Iris settings for + * @return The raw Iris settings for the course + */ public IrisCourseSettings getRawIrisSettingsFor(Course course) { return irisSettingsRepository.findCourseSettings(course.getId()).orElse(getDefaultSettingsFor(course)); } + /** + * Get the raw (uncombined) Iris settings for an exercise. + * If no Iris settings for the exercise exist, the default settings are returned. + * + * @param exercise The exercise to get the Iris settings for + * @return The raw Iris settings for the exercise + */ public IrisExerciseSettings getRawIrisSettingsFor(Exercise exercise) { return irisSettingsRepository.findExerciseSettings(exercise.getId()).orElse(getDefaultSettingsFor(exercise)); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index 73577263b7d2..e575a5649b47 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -3,6 +3,7 @@ import java.util.*; import java.util.function.Function; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import de.tum.in.www1.artemis.domain.iris.IrisTemplate; @@ -13,9 +14,12 @@ import de.tum.in.www1.artemis.service.dto.iris.IrisCombinedHestiaSubSettingsDTO; /** - * Service for handling {@link IrisSettings} objects. + * Service for handling {@link IrisSubSettings} objects. + * This server provides methods to update and combine sub settings objects. + * See {@link IrisSettingsService} for more information about handling {@link IrisSettings}. */ @Service +@Profile("iris") public class IrisSubSettingsService { private final AuthorizationCheckService authCheckService; @@ -24,6 +28,19 @@ public IrisSubSettingsService(AuthorizationCheckService authCheckService) { this.authCheckService = authCheckService; } + /** + * Updates a chat sub settings object. + * If the new settings are null, the current settings will be deleted (except if the parent settings are null == if the settings are global). + * Special notes: + * - If the user is not an admin the rate limit will not be updated. + * - If the user is not an admin the allowed models will not be updated. + * - If the user is not an admin the preferred model will only be updated if it is included in the allowed models. + * + * @param currentSettings Current chat sub settings. + * @param newSettings Updated chat sub settings. + * @param parentSettings Parent chat sub settings. + * @return Updated chat sub settings. + */ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings) { if (newSettings == null) { if (parentSettings == null) { @@ -46,6 +63,18 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS return currentSettings; } + /** + * Updates a Hestia sub settings object. + * If the new settings are null, the current settings will be deleted (except if the parent settings are null == if the settings are global). + * Special notes: + * - If the user is not an admin the allowed models will not be updated. + * - If the user is not an admin the preferred model will only be updated if it is included in the allowed models. + * + * @param currentSettings Current Hestia sub settings. + * @param newSettings Updated Hestia sub settings. + * @param parentSettings Parent Hestia sub settings. + * @return Updated Hestia sub settings. + */ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings) { if (newSettings == null) { if (parentSettings == null) { @@ -64,6 +93,18 @@ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisH return currentSettings; } + /** + * Updates a Code Editor sub settings object. + * If the new settings are null, the current settings will be deleted (except if the parent settings are null == if the settings are global). + * Special notes: + * - If the user is not an admin the allowed models will not be updated. + * - If the user is not an admin the preferred model will only be updated if it is included in the allowed models. + * + * @param currentSettings Current Code Editor sub settings. + * @param newSettings Updated Code Editor sub settings. + * @param parentSettings Parent Code Editor sub settings. + * @return Updated Code Editor sub settings. + */ public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings) { if (newSettings == null) { if (parentSettings == null) { @@ -120,7 +161,7 @@ else if (authCheckService.isAdmin()) { else if (allowedModels != null && allowedModels.contains(newPreferredModel)) { return newPreferredModel; } - else if (parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)) { + else if (allowedModels == null && parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)) { return newPreferredModel; } else { From cbf619b9a0f5d4fedaae9aa831d5d1bb317af90c Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Wed, 25 Oct 2023 16:57:33 +0200 Subject: [PATCH 12/30] Add JavaDocs 2: The revenge of the generics --- .../www1/artemis/service/iris/settings/IrisSettingsService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 5422b1eb7e8a..92240454423d 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -128,6 +128,7 @@ public IrisGlobalSettings getGlobalSettings() { * Save the Iris settings. Should always be used over directly calling the repository. * Automatically decides whether to save a new Iris settings object or update an existing one. * + * @param The subtype of the IrisSettings object * @param settings The Iris settings to save * @return The saved Iris settings */ @@ -144,6 +145,7 @@ public T saveIrisSettings(T settings) { * Save a new IrisSettings object. Should always be used over directly calling the repository. * Ensures that the settings are valid and that no settings for the given object already exist. * + * @param The subtype of the IrisSettings object * @param settings The IrisSettings to save * @return The saved IrisSettings */ From 29476c0367a29b6b81853ba9b2609ff4f73f8a1c Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sat, 28 Oct 2023 02:34:51 +0200 Subject: [PATCH 13/30] Improve UI Nr 1. --- .../iris/settings/IrisSettingsService.java | 4 ++ src/main/webapp/app/admin/admin.route.ts | 5 +- .../course-management-tab-bar.component.html | 11 ++-- .../course-management-tab-bar.component.ts | 9 ---- .../course/manage/course-management.route.ts | 5 ++ ...programming-exercise-detail.component.html | 23 ++++---- .../programming-exercise-detail.component.ts | 12 +---- ...ming-exercise-management-routing.module.ts | 5 ++ ...s-course-settings-update-routing.module.ts | 26 +++++++++ .../iris-course-settings-update.component.ts | 26 ++++++++- ...exercise-settings-update-routing.module.ts | 26 +++++++++ ...iris-exercise-settings-update.component.ts | 27 +++++++++- ...-global-settings-update-routing.module.ts} | 6 ++- .../iris-global-settings-update.component.ts | 17 +++++- ...-common-sub-settings-update.component.html | 54 ++++++++++++------- ...is-common-sub-settings-update.component.ts | 29 ++++++---- .../iris-settings-update.component.ts | 15 ++++-- 17 files changed, 226 insertions(+), 74 deletions(-) create mode 100644 src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update-routing.module.ts create mode 100644 src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update-routing.module.ts rename src/main/webapp/app/iris/settings/{iris-settings-update-routing.module.ts => iris-global-settings-update/iris-global-settings-update-routing.module.ts} (69%) diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 92240454423d..4e2726f6e275 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -208,6 +208,10 @@ else if (existingSettings instanceof IrisExerciseSettings exerciseSettings && se * @return The updated global Iris settings */ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSettings, IrisGlobalSettings settingsUpdate) { + existingSettings.setCurrentVersion(settingsUpdate.getCurrentVersion()); + existingSettings.setEnableAutoUpdateChat(settingsUpdate.isEnableAutoUpdateChat()); + existingSettings.setEnableAutoUpdateHestia(settingsUpdate.isEnableAutoUpdateHestia()); + existingSettings.setEnableAutoUpdateCodeEditor(settingsUpdate.isEnableAutoUpdateCodeEditor()); existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null)); existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null)); existingSettings.setIrisCodeEditorSettings(irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), null)); diff --git a/src/main/webapp/app/admin/admin.route.ts b/src/main/webapp/app/admin/admin.route.ts index 59bdee6d265e..6979fa553b82 100644 --- a/src/main/webapp/app/admin/admin.route.ts +++ b/src/main/webapp/app/admin/admin.route.ts @@ -89,7 +89,10 @@ export const adminState: Routes = [ }, { path: 'iris', - loadChildren: () => import('../iris/settings/iris-settings-update-routing.module').then((module) => module.IrisSettingsUpdateRoutingModule), + loadChildren: () => + import('../iris/settings/iris-global-settings-update/iris-global-settings-update-routing.module').then( + (module) => module.IrisGlobalSettingsUpdateRoutingModule, + ), }, ...organizationMgmtRoute, ...userManagementRoute, diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html index c5539405fadb..19aecc130e3a 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html @@ -22,10 +22,15 @@ Communication - + - Iris - + Iris diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts index 3859bb3a673e..8bbf394b2e91 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts @@ -27,7 +27,6 @@ import { } from '@fortawesome/free-solid-svg-icons'; import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; import { CourseAdminService } from 'app/course/manage/course-admin.service'; -import { IrisCourseSettingsUpdateComponent } from 'app/iris/settings/iris-course-settings-update/iris-course-settings-update.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; @@ -173,12 +172,4 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy { const courseManagementRegex = /course-management\/[0-9]+(\/edit)?$/; return courseManagementRegex.test(this.router.url); } - - /** - * Shows the iris settings in a modal. - */ - showIrisSettings(): void { - const modalRef = this.modalService.open(IrisCourseSettingsUpdateComponent, { size: 'xl' }); - modalRef.componentInstance.courseId = this.course!.id; - } } diff --git a/src/main/webapp/app/course/manage/course-management.route.ts b/src/main/webapp/app/course/manage/course-management.route.ts index fbe905de2c85..d4a93ed99925 100644 --- a/src/main/webapp/app/course/manage/course-management.route.ts +++ b/src/main/webapp/app/course/manage/course-management.route.ts @@ -68,6 +68,11 @@ export const courseManagementState: Routes = [ canActivate: [UserRouteAccessService], loadChildren: () => import('app/grading-system/grading-system.module').then((m) => m.GradingSystemModule), }, + { + path: ':courseId/iris-settings', + loadChildren: () => + import('app/iris/settings/iris-course-settings-update/iris-course-settings-update-routing.module').then((m) => m.IrisCourseSettingsUpdateRoutingModule), + }, { path: ':courseId/tutorial-groups', resolve: { diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html index 32b0006e955b..25fff457695d 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html @@ -59,6 +59,17 @@

Programming Grading + + + + Iris +

diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index 6f318239e2a5..ec1e5d1544b5 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -24,7 +24,7 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { isAdmin: boolean; - allowedIrisModelsInherited: boolean; + inheritAllowedModels: boolean; allowedIrisModels: IrisModel[]; @@ -42,7 +42,7 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { ngOnInit() { this.enabled = this.subSettings?.enabled ?? false; this.allowedIrisModels = this.getAvailableModels(); - this.allowedIrisModelsInherited = !this.subSettings?.allowedModels && this.parentSubSettings !== undefined; + this.inheritAllowedModels = !this.subSettings?.allowedModels && this.parentSubSettings !== undefined; } ngOnChanges(changes: SimpleChanges): void { @@ -62,8 +62,13 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { return this.allIrisModels.find((model) => model.id === this.subSettings?.preferredModel)?.name ?? this.subSettings?.preferredModel ?? 'Inherit'; } - onAllowedIrisModelsSelectionChange() { - this.allowedIrisModelsInherited = false; + onAllowedIrisModelsSelectionChange(model: IrisModel) { + this.inheritAllowedModels = false; + if (this.allowedIrisModels.includes(model)) { + this.allowedIrisModels = this.allowedIrisModels.filter((m) => m !== model); + } else { + this.allowedIrisModels.push(model); + } this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); } @@ -71,12 +76,18 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { this.subSettings!.preferredModel = model?.id; } - inheritAllowedModels() { - this.allowedIrisModelsInherited = true; - this.subSettings!.allowedModels = undefined; - } - onEnabledChange() { this.subSettings!.enabled = this.enabled; } + + onInheritAllowedModelsChange() { + if (this.inheritAllowedModels) { + this.inheritAllowedModels = true; + this.subSettings!.allowedModels = undefined; + this.allowedIrisModels = this.getAvailableModels(); + } else { + this.inheritAllowedModels = false; + this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); + } + } } diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 55c33df74f6f..953eaaf62423 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -8,12 +8,13 @@ import { ButtonType } from 'app/shared/components/button.component'; import { faRotate, faSave } from '@fortawesome/free-solid-svg-icons'; import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; @Component({ selector: 'jhi-iris-settings-update', templateUrl: './iris-settings-update.component.html', }) -export class IrisSettingsUpdateComponent implements OnInit { +export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactivate { @Input() public settingType: IrisSettingsType; @Input() @@ -25,9 +26,10 @@ export class IrisSettingsUpdateComponent implements OnInit { public parentIrisSettings?: IrisSettings; public allIrisModels?: IrisModel[]; - // Loading bools + // Status bools isLoading = false; isSaving = false; + isDirty = false; // Button types PRIMARY = ButtonType.PRIMARY; SUCCESS = ButtonType.SUCCESS; @@ -48,6 +50,12 @@ export class IrisSettingsUpdateComponent implements OnInit { this.loadIrisSettings(); } + canDeactivateWarning?: string; + + canDeactivate(): boolean { + return !this.isDirty; + } + loadIrisModels(): void { this.irisSettingsService.getIrisModels().subscribe((models) => { this.allIrisModels = models; @@ -63,7 +71,7 @@ export class IrisSettingsUpdateComponent implements OnInit { this.alertService.error('artemisApp.iris.settings.error.noSettings'); } this.irisSettings = settings; - console.log(this.irisSettings); + this.isDirty = false; }); this.loadParentIrisSettingsObservable().subscribe((settings) => { if (!settings) { @@ -78,6 +86,7 @@ export class IrisSettingsUpdateComponent implements OnInit { this.saveIrisSettingsObservable().subscribe( (response) => { this.isSaving = false; + this.isDirty = false; this.irisSettings = response.body ?? undefined; this.alertService.success('artemisApp.iris.settings.success'); }, From 983a12638b1c661808513a7a376a7ed32241c4ff Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Tue, 31 Oct 2023 14:20:22 +0100 Subject: [PATCH 14/30] Improve the UI Nr 2. --- .../iris/settings/IrisSettingsService.java | 19 ++++--- .../iris/settings/IrisSubSettingsService.java | 21 +++++--- src/main/webapp/app/iris/iris.module.ts | 2 + ...iris-course-settings-update.component.html | 2 +- ...is-exercise-settings-update.component.html | 4 +- ...iris-global-settings-update.component.html | 2 +- ...is-chat-sub-settings-update.component.html | 11 ++-- ...iris-chat-sub-settings-update.component.ts | 10 ++-- ...-common-sub-settings-update.component.html | 52 +++++++++++-------- ...is-common-sub-settings-update.component.ts | 18 +++++-- ...-autoupdate-settings-update.component.html | 14 +++++ ...al-autoupdate-settings-update.component.ts | 15 ++++++ ...-hestia-sub-settings-update.component.html | 13 ++--- ...is-hestia-sub-settings-update.component.ts | 8 +-- .../iris-settings-update.component.html | 25 ++++++--- .../iris-settings-update.component.ts | 19 ++++--- src/main/webapp/i18n/de/iris.json | 27 +++++++--- src/main/webapp/i18n/en/iris.json | 25 ++++++--- .../iris-settings-update.component.spec.ts | 12 ++--- 19 files changed, 197 insertions(+), 102 deletions(-) create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index 4e2726f6e275..d468cabfb1cf 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -1,5 +1,7 @@ package de.tum.in.www1.artemis.service.iris.settings; +import static de.tum.in.www1.artemis.domain.iris.settings.IrisSettingsType.*; + import java.util.ArrayList; import java.util.Comparator; import java.util.Objects; @@ -212,9 +214,10 @@ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSetti existingSettings.setEnableAutoUpdateChat(settingsUpdate.isEnableAutoUpdateChat()); existingSettings.setEnableAutoUpdateHestia(settingsUpdate.isEnableAutoUpdateHestia()); existingSettings.setEnableAutoUpdateCodeEditor(settingsUpdate.isEnableAutoUpdateCodeEditor()); - existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null)); - existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null)); - existingSettings.setIrisCodeEditorSettings(irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), null)); + existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null, GLOBAL)); + existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null, GLOBAL)); + existingSettings + .setIrisCodeEditorSettings(irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), null, GLOBAL)); return irisSettingsRepository.save(existingSettings); } @@ -228,11 +231,11 @@ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSetti private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSettings, IrisCourseSettings settingsUpdate) { var parentSettings = getCombinedIrisGlobalSettings(); existingSettings.setIrisChatSettings( - irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings())); + irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings(), COURSE)); existingSettings.setIrisHestiaSettings( - irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), parentSettings.irisHestiaSettings())); - existingSettings.setIrisCodeEditorSettings( - irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), parentSettings.irisCodeEditorSettings())); + irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), parentSettings.irisHestiaSettings(), COURSE)); + existingSettings.setIrisCodeEditorSettings(irisSubSettingsService.update(existingSettings.getIrisCodeEditorSettings(), settingsUpdate.getIrisCodeEditorSettings(), + parentSettings.irisCodeEditorSettings(), COURSE)); return irisSettingsRepository.save(existingSettings); } @@ -246,7 +249,7 @@ private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSetti private IrisExerciseSettings updateExerciseSettings(IrisExerciseSettings existingSettings, IrisExerciseSettings settingsUpdate) { var parentSettings = getCombinedIrisSettingsFor(existingSettings.getExercise().getCourseViaExerciseGroupOrCourseMember(), false); existingSettings.setIrisChatSettings( - irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings())); + irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings(), EXERCISE)); return irisSettingsRepository.save(existingSettings); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index e575a5649b47..81704d0aba0d 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -41,7 +41,8 @@ public IrisSubSettingsService(AuthorizationCheckService authCheckService) { * @param parentSettings Parent chat sub settings. * @return Updated chat sub settings. */ - public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings) { + public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings, + IrisSettingsType settingsType) { if (newSettings == null) { if (parentSettings == null) { throw new IllegalArgumentException("Cannot delete the chat settings"); @@ -51,7 +52,9 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS if (currentSettings == null) { currentSettings = new IrisChatSubSettings(); } - currentSettings.setEnabled(newSettings.isEnabled()); + if (settingsType == IrisSettingsType.EXERCISE || authCheckService.isAdmin()) { + currentSettings.setEnabled(newSettings.isEnabled()); + } if (authCheckService.isAdmin()) { currentSettings.setRateLimit(newSettings.getRateLimit()); currentSettings.setRateLimitTimeframeHours(newSettings.getRateLimitTimeframeHours()); @@ -75,7 +78,8 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS * @param parentSettings Parent Hestia sub settings. * @return Updated Hestia sub settings. */ - public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings) { + public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings, + IrisSettingsType settingsType) { if (newSettings == null) { if (parentSettings == null) { throw new IllegalArgumentException("Cannot delete the Hestia settings"); @@ -85,7 +89,9 @@ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisH if (currentSettings == null) { currentSettings = new IrisHestiaSubSettings(); } - currentSettings.setEnabled(newSettings.isEnabled()); + if (settingsType == IrisSettingsType.EXERCISE || authCheckService.isAdmin()) { + currentSettings.setEnabled(newSettings.isEnabled()); + } currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), parentSettings != null ? parentSettings.getAllowedModels() : null)); @@ -105,7 +111,8 @@ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisH * @param parentSettings Parent Code Editor sub settings. * @return Updated Code Editor sub settings. */ - public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings) { + public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings, + IrisSettingsType settingsType) { if (newSettings == null) { if (parentSettings == null) { throw new IllegalArgumentException("Cannot delete the Code Editor settings"); @@ -115,7 +122,9 @@ public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSetting if (currentSettings == null) { currentSettings = new IrisCodeEditorSubSettings(); } - currentSettings.setEnabled(newSettings.isEnabled()); + if (settingsType == IrisSettingsType.EXERCISE || authCheckService.isAdmin()) { + currentSettings.setEnabled(newSettings.isEnabled()); + } currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), parentSettings != null ? parentSettings.getAllowedModels() : null)); diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index 015241fe175e..d603bfb6cee2 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -18,6 +18,7 @@ import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exer import { IrisLogoComponent } from './iris-logo/iris-logo.component'; import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; +import { IrisGlobalAutoupdateSettingsUpdateComponent } from './settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; @NgModule({ declarations: [ @@ -32,6 +33,7 @@ import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-set IrisLogoComponent, IrisChatSubSettingsUpdateComponent, IrisHestiaSubSettingsUpdateComponent, + IrisGlobalAutoupdateSettingsUpdateComponent, ], imports: [CommonModule, MatDialogModule, FormsModule, FontAwesomeModule, ArtemisSharedModule, ArtemisMarkdownModule, ArtemisSharedComponentModule, RouterModule], providers: [], diff --git a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html index 0a0f3c96cda4..05a957df50b5 100644 --- a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html @@ -1,4 +1,4 @@

Course Iris Settings

- +
diff --git a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html index e8f9278177a5..f163c2a1402f 100644 --- a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component.html @@ -1,4 +1,4 @@
-

Programming Exercise Iris Settings

- +

Exercise Iris Settings

+
diff --git a/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.html index 738f8c050ff0..552daf664eaa 100644 --- a/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-global-settings-update/iris-global-settings-update.component.html @@ -1,4 +1,4 @@

Global Iris Settings

- +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html index 8b3dacc84745..ca6429c85227 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.html @@ -18,14 +18,11 @@ />
-
- -
+
+

Template

+
- - +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts index 01e6d912d4f0..df6192fa55e3 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component.ts @@ -1,6 +1,6 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisChatSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisChatSubSettings, IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; @Component({ selector: 'jhi-iris-chat-sub-settings-update', @@ -13,12 +13,12 @@ export class IrisChatSubSettingsUpdateComponent implements OnInit, OnChanges { @Input() parentSubSettings?: IrisChatSubSettings; - @Input() - templateOptional = false; - @Input() rateLimitSettable = false; + @Output() + onChanges = new EventEmitter(); + previousTemplate?: IrisTemplate; isAdmin: boolean; diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html index 9f310a83879a..5b37336d4c1b 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html @@ -1,11 +1,18 @@
- +

Models

-Allowed Models: -
+Allowed Models: +
- +
@@ -36,23 +43,26 @@

Preferred Model:

-
- -
- - +
+ + +
+ {{ getPreferredModelNameParent() }}
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index ec1e5d1544b5..2de88118cc0a 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -1,9 +1,10 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; import { AccountService } from 'app/core/auth/account.service'; import { ButtonType } from 'app/shared/components/button.component'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; @Component({ selector: 'jhi-iris-common-sub-settings-update', @@ -20,7 +21,10 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { allIrisModels: IrisModel[]; @Input() - modelOptional = false; + settingsType: IrisSettingsType; + + @Output() + onChanges = new EventEmitter(); isAdmin: boolean; @@ -30,6 +34,8 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { enabled: boolean; + // Settings types + EXERCISE = IrisSettingsType.EXERCISE; // Button types WARNING = ButtonType.WARNING; // Icons @@ -58,8 +64,12 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { return this.allIrisModels.filter((model) => (this.subSettings?.allowedModels ?? this.parentSubSettings?.allowedModels ?? []).includes(model.id)); } - getSelectedModelName(): string { - return this.allIrisModels.find((model) => model.id === this.subSettings?.preferredModel)?.name ?? this.subSettings?.preferredModel ?? 'Inherit'; + getPreferredModelName(): string | undefined { + return this.allIrisModels.find((model) => model.id === this.subSettings?.preferredModel)?.name ?? this.subSettings?.preferredModel; + } + + getPreferredModelNameParent(): string | undefined { + return this.allIrisModels.find((model) => model.id === this.parentSubSettings?.preferredModel)?.name ?? this.parentSubSettings?.preferredModel; } onAllowedIrisModelsSelectionChange(model: IrisModel) { diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html new file mode 100644 index 000000000000..2decab80c5d4 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html @@ -0,0 +1,14 @@ +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts new file mode 100644 index 000000000000..404132633566 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts @@ -0,0 +1,15 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; + +@Component({ + selector: 'jhi-iris-global-autoupdate-settings-update', + templateUrl: './iris-global-autoupdate-settings-update.component.html', +}) +export class IrisGlobalAutoupdateSettingsUpdateComponent { + @Input() + irisSettings?: IrisGlobalSettings; + + @Output() + onChanges = new EventEmitter(); +} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html index 00890c975fc5..b3ff6ad8c627 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.html @@ -1,11 +1,8 @@ -
- -
- +
+

Template

+
+
- - +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts index c294ed057807..d2055c2ff6ac 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts @@ -1,6 +1,6 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisHestiaSubSettings, IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; @Component({ selector: 'jhi-iris-hestia-sub-settings-update', @@ -13,8 +13,8 @@ export class IrisHestiaSubSettingsUpdateComponent implements OnInit { @Input() parentSubSettings?: IrisHestiaSubSettings; - @Input() - templateOptional = false; + @Output() + onChanges = new EventEmitter(); previousTemplate?: IrisTemplate; diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html index 8ec959e544a3..c887b0197325 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html @@ -3,47 +3,56 @@
+
+
+

Auto-Update Settings

+ +
+

Chat Settings

-
+

Hestia Settings

-
+

Code Editor Settings

diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 953eaaf62423..9abdc707c6f3 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { IrisSettings, IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; import { HttpResponse } from '@angular/common/http'; @@ -14,9 +14,9 @@ import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; selector: 'jhi-iris-settings-update', templateUrl: './iris-settings-update.component.html', }) -export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactivate { +export class IrisSettingsUpdateComponent implements OnInit, OnChanges, ComponentCanDeactivate { @Input() - public settingType: IrisSettingsType; + public settingsType: IrisSettingsType; @Input() public courseId?: number; @Input() @@ -50,6 +50,13 @@ export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactiva this.loadIrisSettings(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.irisSettings && changes.irisSettings.previousValue) { + this.isDirty = true; + console.log('dirty'); + } + } + canDeactivateWarning?: string; canDeactivate(): boolean { @@ -98,7 +105,7 @@ export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactiva } loadParentIrisSettingsObservable(): Observable { - switch (this.settingType) { + switch (this.settingsType) { case IrisSettingsType.GLOBAL: // Global settings have no parent return new Observable(); @@ -110,7 +117,7 @@ export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactiva } loadIrisSettingsObservable(): Observable { - switch (this.settingType) { + switch (this.settingsType) { case IrisSettingsType.GLOBAL: return this.irisSettingsService.getGlobalSettings(); case IrisSettingsType.COURSE: @@ -121,7 +128,7 @@ export class IrisSettingsUpdateComponent implements OnInit, ComponentCanDeactiva } saveIrisSettingsObservable(): Observable> { - switch (this.settingType) { + switch (this.settingsType) { case IrisSettingsType.GLOBAL: return this.irisSettingsService.setGlobalSettings(this.irisSettings!); case IrisSettingsType.COURSE: diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index 25e456e7975d..4dd02d161f06 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -23,18 +23,22 @@ "chatSettings": "Chat Einstellungen", "hestiaSettings": "Hestia Einstellungen", "codeEditorSettings": "Code Editor Einstellungen", - "inheritChatSettings": "Vererbe Chat Einstellungen", - "inheritHestiaSettings": "Vererbe Hestia Einstellungen", "enabled-disabled": "Aktiviert/Deaktiviert", - "allowedModels": { - "title": "Erlaubte Modelle", - "inheritedInfo": "Diese Modelle sind von einem höherem Level geerbt. Ändere sie hier, um sie in diesem Level zu überschreiben.", - "inheritButton": "Erlaubte Modelle vererben" + "models": { + "title": "Modelle", + "allowedModels": { + "title": "Erlaubte Modelle", + "inheritSwitch": "Erbe erlaubte Modelle" + }, + "preferredModel": { + "title": "Präferiertes Modell", + "inherit": "Erben" + } }, - "preferredModel": "Präferiertes Modell", - "inheritModel": "Vererbe Modell", "rateLimit": "Rate Limit", "rateLimitTooltip": "Die maximale Anzahl an Antworten, die ein Benutzer vom LLM in einem bestimmten Zeitraum erhalten kann.", + "rateLimitTimeframeHours": "Rate Limit Zeitrahmen (Stunden)", + "rateLimitTimeframeHoursTooltip": "Der Zeitraum, in welchem das Rate Limit angewendet wird, in Stunden.", "template": { "title": "Template", "inherit": "Template erben", @@ -45,6 +49,13 @@ "global": "Globale Iris Einstellungen", "course": "Kurs Iris Einstellungen", "programmingExercise": "Programmieraufgabe Iris Einstellungen" + }, + "autoUpdate": { + "title": "Auto Update Einstellungen", + "tooltip": "Wenn aktiviert, werden die spezifischen globalen Iris Einstellungen automatisch aktualisiert, wenn eine neue Version von Artemis neue Iris Einstellungen bereitstellt.", + "chatLabel": "Auto Update der Chat Einstellungen", + "hestiaLabel": "Auto Update der Hestia Einstellungen", + "codeEditorLabel": "Auto Update der Code Editor Einstellungen" } }, "error": { diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index 8eb5d01dcfed..685e48d57d1e 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -24,13 +24,17 @@ "hestiaSettings": "Hestia Settings", "codeEditorSettings": "Code Editor Settings", "enabled-disabled": "Enabled/Disabled", - "allowedModels": { - "title": "Allowed Models", - "inheritedInfo": "These models are inherited from higher level settings. Edit them here to overwrite them for this level.", - "inheritButton": "Inherit Allowed Models" + "models": { + "title": "Models", + "allowedModels": { + "title": "Allowed Models", + "inheritSwitch": "Inherit Allowed Models" + }, + "preferredModel": { + "title": "Preferred Model", + "inherit": "Inherit" + } }, - "preferredModel": "Preferred Model", - "inheritModel": "Inherit Model", "rateLimit": "Rate Limit", "rateLimitTooltip": "The maximum number of answers a user can receive from the LLM in a given time period.", "rateLimitTimeframeHours": "Rate Limit Timeframe (Hours)", @@ -44,7 +48,14 @@ "title": { "global": "Global Iris Settings", "course": "Course Iris Settings", - "programmingExercise": "Programming Exercise Iris Settings" + "exercise": "Exercise Iris Settings" + }, + "autoUpdate": { + "title": "Auto Update Settings", + "tooltip": "If enabled, the specific global Iris settings will be automatically updated when a new release of Artemis provides new Iris settings.", + "chatLabel": "Auto Update Chat Settings", + "hestiaLabel": "Auto Update Hestia Settings", + "codeEditorLabel": "Auto Update Code Editor Settings" } }, "error": { diff --git a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts index 0bed904d03eb..e91dded3a4dd 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts @@ -73,7 +73,7 @@ describe('IrisSettingsUpdateComponent Component', () => { const irisSettings = baseSettings(); const getSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - comp.settingType = IrisSettingsType.GLOBAL; + comp.settingsType = IrisSettingsType.GLOBAL; fixture.detectChanges(); expect(getSettingsSpy).toHaveBeenCalledOnce(); expect(getModelsSpy).toHaveBeenCalledOnce(); @@ -85,7 +85,7 @@ describe('IrisSettingsUpdateComponent Component', () => { const irisSettings = baseSettings(); const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedCourseSettings').mockReturnValue(of(irisSettings)); const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - comp.settingType = IrisSettingsType.COURSE; + comp.settingsType = IrisSettingsType.COURSE; comp.courseId = 1; fixture.detectChanges(); expect(getSettingsSpy).toHaveBeenCalledWith(1); @@ -98,7 +98,7 @@ describe('IrisSettingsUpdateComponent Component', () => { const irisSettings = baseSettings(); const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedProgrammingExerciseSettings').mockReturnValue(of(irisSettings)); const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - comp.settingType = IrisSettingsType.PROGRAMMING_EXERCISE; + comp.settingsType = IrisSettingsType.PROGRAMMING_EXERCISE; comp.programmingExerciseId = 1; fixture.detectChanges(); expect(getSettingsSpy).toHaveBeenCalledWith(1); @@ -112,7 +112,7 @@ describe('IrisSettingsUpdateComponent Component', () => { irisSettings.id = undefined; const irisSettingsSaved = baseSettings(); const setSettingsSpy = jest.spyOn(irisSettingsService, 'setGlobalSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingType = IrisSettingsType.GLOBAL; + comp.settingsType = IrisSettingsType.GLOBAL; comp.irisSettings = irisSettings; comp.saveIrisSettings(); expect(setSettingsSpy).toHaveBeenCalledWith(irisSettings); @@ -124,7 +124,7 @@ describe('IrisSettingsUpdateComponent Component', () => { irisSettings.id = undefined; const irisSettingsSaved = baseSettings(); const setSettingsSpy = jest.spyOn(irisSettingsService, 'setCourseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingType = IrisSettingsType.COURSE; + comp.settingsType = IrisSettingsType.COURSE; comp.courseId = 1; comp.irisSettings = irisSettings; comp.saveIrisSettings(); @@ -137,7 +137,7 @@ describe('IrisSettingsUpdateComponent Component', () => { irisSettings.id = undefined; const irisSettingsSaved = baseSettings(); const setSettingsSpy = jest.spyOn(irisSettingsService, 'setProgrammingExerciseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingType = IrisSettingsType.PROGRAMMING_EXERCISE; + comp.settingsType = IrisSettingsType.PROGRAMMING_EXERCISE; comp.programmingExerciseId = 1; comp.irisSettings = irisSettings; comp.saveIrisSettings(); From be5a61afc84062321340e109b87cdd08b8ce3185 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Tue, 31 Oct 2023 15:07:37 +0100 Subject: [PATCH 15/30] Fix server tests --- .../www1/artemis/iris/AbstractIrisIntegrationTest.java | 10 +++++++--- .../www1/artemis/iris/IrisMessageIntegrationTest.java | 4 ---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java index f0461c59e77c..b1b67f04d2d6 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/AbstractIrisIntegrationTest.java @@ -25,6 +25,7 @@ import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService; import de.tum.in.www1.artemis.repository.CourseRepository; import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository; +import de.tum.in.www1.artemis.repository.iris.IrisSettingsRepository; import de.tum.in.www1.artemis.repository.iris.IrisTemplateRepository; import de.tum.in.www1.artemis.service.iris.IrisWebsocketService; import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; @@ -54,6 +55,9 @@ public abstract class AbstractIrisIntegrationTest extends AbstractSpringIntegrat @Autowired protected ExerciseUtilService exerciseUtilService; + @Autowired + private IrisSettingsRepository irisSettingsRepository; + @Autowired protected ProgrammingExerciseUtilService programmingExerciseUtilService; @@ -75,7 +79,7 @@ protected void activateIrisGlobally() { globalSettings.getIrisChatSettings().setPreferredModel(null); globalSettings.getIrisHestiaSettings().setEnabled(true); globalSettings.getIrisHestiaSettings().setPreferredModel(null); - irisSettingsService.saveIrisSettings(globalSettings); + irisSettingsRepository.save(globalSettings); } protected void activateIrisFor(Course course) { @@ -93,7 +97,7 @@ protected void activateIrisFor(Course course) { courseSettings.getIrisCodeEditorSettings().setSolutionRepoGenerationTemplate(null); courseSettings.getIrisCodeEditorSettings().setTestRepoGenerationTemplate(null); courseSettings.getIrisCodeEditorSettings().setPreferredModel(null); - irisSettingsService.saveIrisSettings(courseSettings); + irisSettingsRepository.save(courseSettings); } protected void activateIrisFor(ProgrammingExercise exercise) { @@ -101,7 +105,7 @@ protected void activateIrisFor(ProgrammingExercise exercise) { exerciseSettings.getIrisChatSettings().setEnabled(true); exerciseSettings.getIrisChatSettings().setTemplate(createDummyTemplate()); exerciseSettings.getIrisChatSettings().setPreferredModel(null); - irisSettingsService.saveIrisSettings(exerciseSettings); + irisSettingsRepository.save(exerciseSettings); } protected IrisTemplate createDummyTemplate() { diff --git a/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java index faff5e1adcd7..a857f9192aca 100644 --- a/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/iris/IrisMessageIntegrationTest.java @@ -27,7 +27,6 @@ import de.tum.in.www1.artemis.repository.iris.IrisMessageRepository; import de.tum.in.www1.artemis.repository.iris.IrisSessionRepository; import de.tum.in.www1.artemis.service.iris.IrisMessageService; -import de.tum.in.www1.artemis.service.iris.IrisRateLimitService; import de.tum.in.www1.artemis.service.iris.IrisSessionService; import de.tum.in.www1.artemis.util.IrisUtilTestService; import de.tum.in.www1.artemis.util.LocalRepository; @@ -54,9 +53,6 @@ class IrisMessageIntegrationTest extends AbstractIrisIntegrationTest { @Autowired private ParticipationUtilService participationUtilService; - @Autowired - private IrisRateLimitService irisRateLimitService; - private ProgrammingExercise exercise; private LocalRepository repository; From 8a4b578dc64b767f3da36d6cbb94ac55c1fa7eb3 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Tue, 31 Oct 2023 22:40:10 +0100 Subject: [PATCH 16/30] Fix client tests --- .../iris/settings/IrisSubSettingsService.java | 3 ++ .../iris-settings-update.component.spec.ts | 44 ++++++++++----- ...iris-sub-settings-update.component.spec.ts | 53 +++++-------------- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index 81704d0aba0d..7fea3e008657 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -39,6 +39,7 @@ public IrisSubSettingsService(AuthorizationCheckService authCheckService) { * @param currentSettings Current chat sub settings. * @param newSettings Updated chat sub settings. * @param parentSettings Parent chat sub settings. + * @param settingsType Type of the settings the sub settings belong to. * @return Updated chat sub settings. */ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatSubSettings newSettings, IrisCombinedChatSubSettingsDTO parentSettings, @@ -76,6 +77,7 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS * @param currentSettings Current Hestia sub settings. * @param newSettings Updated Hestia sub settings. * @param parentSettings Parent Hestia sub settings. + * @param settingsType Type of the settings the sub settings belong to. * @return Updated Hestia sub settings. */ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings, @@ -109,6 +111,7 @@ public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisH * @param currentSettings Current Code Editor sub settings. * @param newSettings Updated Code Editor sub settings. * @param parentSettings Parent Code Editor sub settings. + * @param settingsType Type of the settings the sub settings belong to. * @return Updated Code Editor sub settings. */ public IrisCodeEditorSubSettings update(IrisCodeEditorSubSettings currentSettings, IrisCodeEditorSubSettings newSettings, IrisCombinedCodeEditorSubSettingsDTO parentSettings, diff --git a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts index e91dded3a4dd..c9fe7d126e68 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts @@ -1,33 +1,40 @@ import { ArtemisTestModule } from '../../../test.module'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; -import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; -import { IrisSettingsType, IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisGlobalSettings, IrisSettings, IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; import { MockComponent, MockProvider } from 'ng-mocks'; import { of } from 'rxjs'; -import { IrisSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component'; import { ButtonComponent } from 'app/shared/components/button.component'; import { HttpResponse } from '@angular/common/http'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; +import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; +import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; function baseSettings() { const mockTemplate = new IrisTemplate(); mockTemplate.id = 1; mockTemplate.content = 'Hello World'; - const mockChatSettings = new IrisSubSettings(); + const mockChatSettings = new IrisChatSubSettings(); mockChatSettings.id = 1; mockChatSettings.template = mockTemplate; mockChatSettings.enabled = true; - const mockHestiaSettings = new IrisSubSettings(); + const mockHestiaSettings = new IrisHestiaSubSettings(); mockHestiaSettings.id = 2; mockHestiaSettings.template = mockTemplate; mockHestiaSettings.enabled = true; - const irisSettings = new IrisSettings(); + const mockCodeEditorSettings = new IrisCodeEditorSubSettings(); + mockCodeEditorSettings.id = 2; + mockCodeEditorSettings.enabled = false; + const irisSettings = new IrisGlobalSettings(); irisSettings.id = 1; irisSettings.irisChatSettings = mockChatSettings; irisSettings.irisHestiaSettings = mockHestiaSettings; + irisSettings.irisCodeEditorSettings = mockCodeEditorSettings; return irisSettings; } @@ -54,7 +61,14 @@ describe('IrisSettingsUpdateComponent Component', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ArtemisTestModule], - declarations: [IrisSettingsUpdateComponent, MockComponent(IrisSubSettingsUpdateComponent), MockComponent(ButtonComponent)], + declarations: [ + IrisSettingsUpdateComponent, + MockComponent(IrisCommonSubSettingsUpdateComponent), + MockComponent(IrisChatSubSettingsUpdateComponent), + MockComponent(IrisHestiaSubSettingsUpdateComponent), + MockComponent(IrisGlobalAutoupdateSettingsUpdateComponent), + MockComponent(ButtonComponent), + ], providers: [MockProvider(IrisSettingsService)], }) .compileComponents() @@ -85,26 +99,28 @@ describe('IrisSettingsUpdateComponent Component', () => { const irisSettings = baseSettings(); const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedCourseSettings').mockReturnValue(of(irisSettings)); const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + const getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); comp.settingsType = IrisSettingsType.COURSE; comp.courseId = 1; fixture.detectChanges(); expect(getSettingsSpy).toHaveBeenCalledWith(1); expect(getModelsSpy).toHaveBeenCalledOnce(); + expect(getParentSettingsSpy).toHaveBeenCalledOnce(); expect(comp.irisSettings).toEqual(irisSettings); - expect(fixture.debugElement.nativeElement.querySelector('#inheritHestia')).toBeFalsy(); }); it('Loads programming exercise settings correctly', () => { const irisSettings = baseSettings(); const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedProgrammingExerciseSettings').mockReturnValue(of(irisSettings)); const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - comp.settingsType = IrisSettingsType.PROGRAMMING_EXERCISE; - comp.programmingExerciseId = 1; + const getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getCombinedCourseSettings').mockReturnValue(of(irisSettings)); + comp.settingsType = IrisSettingsType.EXERCISE; + comp.exerciseId = 1; fixture.detectChanges(); expect(getSettingsSpy).toHaveBeenCalledWith(1); expect(getModelsSpy).toHaveBeenCalledOnce(); + expect(getParentSettingsSpy).toHaveBeenCalledOnce(); expect(comp.irisSettings).toEqual(irisSettings); - expect(fixture.debugElement.nativeElement.querySelector('#inheritHestia')).toBeTruthy(); }); it('Saves global settings correctly', () => { @@ -137,8 +153,8 @@ describe('IrisSettingsUpdateComponent Component', () => { irisSettings.id = undefined; const irisSettingsSaved = baseSettings(); const setSettingsSpy = jest.spyOn(irisSettingsService, 'setProgrammingExerciseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingsType = IrisSettingsType.PROGRAMMING_EXERCISE; - comp.programmingExerciseId = 1; + comp.settingsType = IrisSettingsType.EXERCISE; + comp.exerciseId = 1; comp.irisSettings = irisSettings; comp.saveIrisSettings(); expect(setSettingsSpy).toHaveBeenCalledWith(1, irisSettings); diff --git a/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts index bab069444247..da6d60e70740 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts @@ -2,50 +2,33 @@ import { ArtemisTestModule } from '../../../test.module'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; -import { IrisSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-sub-settings-update/iris-sub-settings-update.component'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { IrisChatSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { MockDirective } from 'ng-mocks'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; function baseSettings() { const mockTemplate = new IrisTemplate(); mockTemplate.id = 1; mockTemplate.content = 'Hello World'; - const irisSubSettings = new IrisSubSettings(); + const irisSubSettings = new IrisChatSubSettings(); irisSubSettings.id = 2; irisSubSettings.template = mockTemplate; irisSubSettings.enabled = true; return irisSubSettings; } -function models() { - return [ - { - id: '1', - name: 'Model 1', - description: 'Model 1 Description', - }, - { - id: '2', - name: 'Model 2', - description: 'Model 2 Description', - }, - ] as IrisModel[]; -} - describe('IrisSubSettingsUpdateComponent Component', () => { - let comp: IrisSubSettingsUpdateComponent; - let fixture: ComponentFixture; + let comp: IrisChatSubSettingsUpdateComponent; + let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ imports: [ArtemisTestModule, FormsModule, MockDirective(NgbTooltip)], - declarations: [IrisSubSettingsUpdateComponent], + declarations: [IrisChatSubSettingsUpdateComponent], }).compileComponents(); - fixture = TestBed.createComponent(IrisSubSettingsUpdateComponent); + fixture = TestBed.createComponent(IrisChatSubSettingsUpdateComponent); comp = fixture.componentInstance; - comp.models = models(); }); afterEach(() => { @@ -54,45 +37,35 @@ describe('IrisSubSettingsUpdateComponent Component', () => { it('template is not optional', () => { comp.subSettings = baseSettings(); - comp.templateOptional = false; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeFalsy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); }); - it('template is optional and defined', () => { + it('template is optional', () => { comp.subSettings = baseSettings(); - comp.templateOptional = true; + comp.parentSubSettings = baseSettings(); fixture.detectChanges(); expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); }); - it('template is optional and undefined', () => { - const subSettings = baseSettings(); - subSettings.template = undefined; - comp.subSettings = subSettings; - comp.templateOptional = true; - fixture.detectChanges(); - expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); - expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeFalsy(); - }); - it('template is optional and changes from defined to undefined', () => { comp.subSettings = baseSettings(); - comp.templateOptional = true; + comp.parentSubSettings = baseSettings(); fixture.detectChanges(); comp.onInheritTemplateChanged(); fixture.detectChanges(); expect(comp.subSettings.template).toBeUndefined(); expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); - expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeFalsy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); }); it('template is optional and changes from undefined to defined', () => { const subSettings = baseSettings(); subSettings.template = undefined; comp.subSettings = subSettings; - comp.templateOptional = true; + comp.parentSubSettings = baseSettings(); fixture.detectChanges(); comp.onInheritTemplateChanged(); fixture.detectChanges(); From f5f9ac488f2e7f77792661ecf2482876b6b17784 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Tue, 31 Oct 2023 22:48:41 +0100 Subject: [PATCH 17/30] Fix client tests II --- .../iris/settings/iris-settings.service.spec.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts index e067fde8f433..7d1110f8ee77 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts @@ -2,26 +2,30 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { take } from 'rxjs/operators'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; -import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; function mockSettings() { const mockTemplate = new IrisTemplate(); mockTemplate.id = 1; mockTemplate.content = 'Hello World'; - const mockChatSettings = new IrisSubSettings(); + const mockChatSettings = new IrisChatSubSettings(); mockChatSettings.id = 1; mockChatSettings.template = mockTemplate; mockChatSettings.enabled = true; - const mockHestiaSettings = new IrisSubSettings(); + const mockHestiaSettings = new IrisHestiaSubSettings(); mockHestiaSettings.id = 2; mockHestiaSettings.template = mockTemplate; mockHestiaSettings.enabled = true; - const irisSettings = new IrisSettings(); + const mockCodeEditorSettings = new IrisCodeEditorSubSettings(); + mockCodeEditorSettings.id = 2; + mockCodeEditorSettings.enabled = false; + const irisSettings = new IrisGlobalSettings(); irisSettings.id = 1; irisSettings.irisChatSettings = mockChatSettings; irisSettings.irisHestiaSettings = mockHestiaSettings; + irisSettings.irisCodeEditorSettings = mockCodeEditorSettings; return irisSettings; } From d147dead26f52a29dabfcbd76c3ba2a47d9ed18b Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 03:14:10 +0100 Subject: [PATCH 18/30] Improve client tests --- .../iris-settings-update.component.ts | 11 -- ...s-course-settings-update.component.spec.ts | 103 +++++++++++ ...exercise-settings-update.component.spec.ts | 104 +++++++++++ ...s-global-settings-update.component.spec.ts | 90 ++++++++++ .../iris-settings-update.component.spec.ts | 163 ------------------ .../settings/iris-settings.service.spec.ts | 27 +-- .../component/iris/settings/mock-settings.ts | 42 +++++ 7 files changed, 340 insertions(+), 200 deletions(-) create mode 100644 src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts create mode 100644 src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts create mode 100644 src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts delete mode 100644 src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts create mode 100644 src/test/javascript/spec/component/iris/settings/mock-settings.ts diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 9abdc707c6f3..1771ed526c1d 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -6,7 +6,6 @@ import { Observable } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { ButtonType } from 'app/shared/components/button.component'; import { faRotate, faSave } from '@fortawesome/free-solid-svg-icons'; -import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; @@ -137,14 +136,4 @@ export class IrisSettingsUpdateComponent implements OnInit, OnChanges, Component return this.irisSettingsService.setProgrammingExerciseSettings(this.exerciseId!, this.irisSettings!); } } - - onInheritHestiaSettingsChanged() { - if (this.irisSettings?.irisHestiaSettings) { - this.irisSettings!.irisHestiaSettings = undefined; - } else { - const irisSubSettings = new IrisHestiaSubSettings(); - irisSubSettings.enabled = true; - this.irisSettings!.irisHestiaSettings = irisSubSettings; - } - } } diff --git a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts new file mode 100644 index 000000000000..8658e39a7b08 --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts @@ -0,0 +1,103 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; +import { MockComponent, MockDirective, MockProvider } from 'ng-mocks'; +import { BehaviorSubject, of } from 'rxjs'; +import { ButtonComponent } from 'app/shared/components/button.component'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; +import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; +import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; +import { mockSettings, models } from './mock-settings'; +import { ActivatedRoute, Params } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgModel } from '@angular/forms'; +import { IrisCourseSettingsUpdateComponent } from 'app/iris/settings/iris-course-settings-update/iris-course-settings-update.component'; +import { By } from '@angular/platform-browser'; +import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { HttpResponse } from '@angular/common/http'; + +describe('IrisCourseSettingsUpdateComponent Component', () => { + let comp: IrisCourseSettingsUpdateComponent; + let fixture: ComponentFixture; + let irisSettingsService: IrisSettingsService; + const routeParamsSubject = new BehaviorSubject({ courseId: 1 }); + const route = { parent: { params: routeParamsSubject.asObservable() } } as ActivatedRoute; + let paramsSpy: jest.SpyInstance; + let getSettingsSpy: jest.SpyInstance; + let getModelsSpy: jest.SpyInstance; + let getParentSettingsSpy: jest.SpyInstance; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, RouterTestingModule], + declarations: [ + IrisCourseSettingsUpdateComponent, + IrisSettingsUpdateComponent, + MockComponent(IrisCommonSubSettingsUpdateComponent), + MockComponent(IrisChatSubSettingsUpdateComponent), + MockComponent(IrisHestiaSubSettingsUpdateComponent), + MockComponent(IrisGlobalAutoupdateSettingsUpdateComponent), + MockComponent(ButtonComponent), + MockDirective(NgModel), + ], + providers: [MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], + }) + .compileComponents() + .then(() => { + irisSettingsService = TestBed.inject(IrisSettingsService); + + // Setup + routeParamsSubject.next({ courseId: 1 }); + paramsSpy = jest.spyOn(route.parent!.params, 'subscribe'); + + const irisSettings = mockSettings(); + getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedCourseSettings').mockReturnValue(of(irisSettings)); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); + }); + fixture = TestBed.createComponent(IrisCourseSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('Setup works correctly', () => { + fixture.detectChanges(); + expect(paramsSpy).toHaveBeenCalledOnce(); + expect(comp.courseId).toBe(1); + expect(comp.settingsUpdateComponent).toBeTruthy(); + expect(getSettingsSpy).toHaveBeenCalledWith(1); + expect(getModelsSpy).toHaveBeenCalledOnce(); + expect(getParentSettingsSpy).toHaveBeenCalledOnce(); + + expect(fixture.debugElement.query(By.directive(IrisGlobalAutoupdateSettingsUpdateComponent))).toBeFalsy(); + expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(3); + expect(fixture.debugElement.query(By.directive(IrisChatSubSettingsUpdateComponent))).toBeTruthy(); + expect(fixture.debugElement.query(By.directive(IrisHestiaSubSettingsUpdateComponent))).toBeTruthy(); + }); + + it('Can deactivate correctly', () => { + fixture.detectChanges(); + expect(comp.canDeactivate()).toBeTrue(); + comp.settingsUpdateComponent!.isDirty = true; + expect(comp.canDeactivate()).toBeFalse(); + comp.settingsUpdateComponent!.canDeactivateWarning = 'Warning'; + expect(comp.canDeactivateWarning).toBe('Warning'); + }); + + it('Saves settings correctly', () => { + fixture.detectChanges(); + const irisSettings = mockSettings(); + irisSettings.id = undefined; + const irisSettingsSaved = mockSettings(); + const setSettingsSpy = jest.spyOn(irisSettingsService, 'setCourseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); + comp.settingsUpdateComponent!.irisSettings = irisSettings; + comp.settingsUpdateComponent!.saveIrisSettings(); + expect(setSettingsSpy).toHaveBeenCalledWith(1, irisSettings); + expect(comp.settingsUpdateComponent!.irisSettings).toEqual(irisSettingsSaved); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts new file mode 100644 index 000000000000..66b00eeabee4 --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts @@ -0,0 +1,104 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; +import { MockComponent, MockDirective, MockProvider } from 'ng-mocks'; +import { BehaviorSubject, of } from 'rxjs'; +import { ButtonComponent } from 'app/shared/components/button.component'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; +import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; +import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; +import { mockSettings, models } from './mock-settings'; +import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component'; +import { ActivatedRoute, Params } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgModel } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { HttpResponse } from '@angular/common/http'; + +describe('IrisExerciseSettingsUpdateComponent Component', () => { + let comp: IrisExerciseSettingsUpdateComponent; + let fixture: ComponentFixture; + let irisSettingsService: IrisSettingsService; + const routeParamsSubject = new BehaviorSubject({ courseId: 1, exerciseId: 1 }); + const route = { parent: { params: routeParamsSubject.asObservable() } } as ActivatedRoute; + let paramsSpy: jest.SpyInstance; + let getSettingsSpy: jest.SpyInstance; + let getModelsSpy: jest.SpyInstance; + let getParentSettingsSpy: jest.SpyInstance; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, RouterTestingModule], + declarations: [ + IrisExerciseSettingsUpdateComponent, + IrisSettingsUpdateComponent, + MockComponent(IrisCommonSubSettingsUpdateComponent), + MockComponent(IrisChatSubSettingsUpdateComponent), + MockComponent(IrisHestiaSubSettingsUpdateComponent), + MockComponent(IrisGlobalAutoupdateSettingsUpdateComponent), + MockComponent(ButtonComponent), + MockDirective(NgModel), + ], + providers: [MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], + }) + .compileComponents() + .then(() => { + irisSettingsService = TestBed.inject(IrisSettingsService); + + // Setup + routeParamsSubject.next({ courseId: 1, exerciseId: 2 }); + paramsSpy = jest.spyOn(route.parent!.params, 'subscribe'); + + const irisSettings = mockSettings(); + getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedProgrammingExerciseSettings').mockReturnValue(of(irisSettings)); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getCombinedCourseSettings').mockReturnValue(of(irisSettings)); + }); + fixture = TestBed.createComponent(IrisExerciseSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('Setup works correctly', () => { + fixture.detectChanges(); + expect(paramsSpy).toHaveBeenCalledOnce(); + expect(comp.courseId).toBe(1); + expect(comp.exerciseId).toBe(2); + expect(comp.settingsUpdateComponent).toBeTruthy(); + expect(getSettingsSpy).toHaveBeenCalledWith(2); + expect(getModelsSpy).toHaveBeenCalledOnce(); + expect(getParentSettingsSpy).toHaveBeenCalledWith(1); + + expect(fixture.debugElement.query(By.directive(IrisGlobalAutoupdateSettingsUpdateComponent))).toBeFalsy(); + expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(1); + expect(fixture.debugElement.query(By.directive(IrisChatSubSettingsUpdateComponent))).toBeTruthy(); + expect(fixture.debugElement.query(By.directive(IrisHestiaSubSettingsUpdateComponent))).toBeFalsy(); + }); + + it('Can deactivate correctly', () => { + fixture.detectChanges(); + expect(comp.canDeactivate()).toBeTrue(); + comp.settingsUpdateComponent!.isDirty = true; + expect(comp.canDeactivate()).toBeFalse(); + comp.settingsUpdateComponent!.canDeactivateWarning = 'Warning'; + expect(comp.canDeactivateWarning).toBe('Warning'); + }); + + it('Saves settings correctly', () => { + fixture.detectChanges(); + const irisSettings = mockSettings(); + irisSettings.id = undefined; + const irisSettingsSaved = mockSettings(); + const setSettingsSpy = jest.spyOn(irisSettingsService, 'setProgrammingExerciseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); + comp.settingsUpdateComponent!.irisSettings = irisSettings; + comp.settingsUpdateComponent!.saveIrisSettings(); + expect(setSettingsSpy).toHaveBeenCalledWith(2, irisSettings); + expect(comp.settingsUpdateComponent!.irisSettings).toEqual(irisSettingsSaved); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts new file mode 100644 index 000000000000..c03572224d89 --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts @@ -0,0 +1,90 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; +import { MockComponent, MockDirective, MockProvider } from 'ng-mocks'; +import { of } from 'rxjs'; +import { ButtonComponent } from 'app/shared/components/button.component'; +import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; +import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; +import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; +import { mockSettings, models } from './mock-settings'; +import { NgModel } from '@angular/forms'; +import { IrisGlobalSettingsUpdateComponent } from 'app/iris/settings/iris-global-settings-update/iris-global-settings-update.component'; +import { By } from '@angular/platform-browser'; +import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { HttpResponse } from '@angular/common/http'; + +describe('IrisGlobalSettingsUpdateComponent Component', () => { + let comp: IrisGlobalSettingsUpdateComponent; + let fixture: ComponentFixture; + let irisSettingsService: IrisSettingsService; + let getSettingsSpy: jest.SpyInstance; + let getModelsSpy: jest.SpyInstance; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule], + declarations: [ + IrisGlobalSettingsUpdateComponent, + IrisSettingsUpdateComponent, + MockComponent(IrisCommonSubSettingsUpdateComponent), + MockComponent(IrisChatSubSettingsUpdateComponent), + MockComponent(IrisHestiaSubSettingsUpdateComponent), + MockComponent(IrisGlobalAutoupdateSettingsUpdateComponent), + MockComponent(ButtonComponent), + MockDirective(NgModel), + ], + providers: [MockProvider(IrisSettingsService)], + }) + .compileComponents() + .then(() => { + irisSettingsService = TestBed.inject(IrisSettingsService); + + // Setup + const irisSettings = mockSettings(); + getSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + }); + fixture = TestBed.createComponent(IrisGlobalSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('Setup works correctly', () => { + fixture.detectChanges(); + expect(comp.settingsUpdateComponent).toBeTruthy(); + expect(getSettingsSpy).toHaveBeenCalledOnce(); + expect(getModelsSpy).toHaveBeenCalledOnce(); + + expect(fixture.debugElement.query(By.directive(IrisGlobalAutoupdateSettingsUpdateComponent))).toBeTruthy(); + expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(3); + expect(fixture.debugElement.query(By.directive(IrisChatSubSettingsUpdateComponent))).toBeTruthy(); + expect(fixture.debugElement.query(By.directive(IrisHestiaSubSettingsUpdateComponent))).toBeTruthy(); + }); + + it('Can deactivate correctly', () => { + fixture.detectChanges(); + expect(comp.canDeactivate()).toBeTrue(); + comp.settingsUpdateComponent!.isDirty = true; + expect(comp.canDeactivate()).toBeFalse(); + comp.settingsUpdateComponent!.canDeactivateWarning = 'Warning'; + expect(comp.canDeactivateWarning).toBe('Warning'); + }); + + it('Saves settings correctly', () => { + fixture.detectChanges(); + const irisSettings = mockSettings(); + irisSettings.id = undefined; + const irisSettingsSaved = mockSettings(); + const setSettingsSpy = jest.spyOn(irisSettingsService, 'setGlobalSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); + comp.settingsUpdateComponent!.irisSettings = irisSettings; + comp.settingsUpdateComponent!.saveIrisSettings(); + expect(setSettingsSpy).toHaveBeenCalledWith(irisSettings); + expect(comp.settingsUpdateComponent!.irisSettings).toEqual(irisSettingsSaved); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts deleted file mode 100644 index c9fe7d126e68..000000000000 --- a/src/test/javascript/spec/component/iris/settings/iris-settings-update.component.spec.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { ArtemisTestModule } from '../../../test.module'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; -import { IrisGlobalSettings, IrisSettings, IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; -import { IrisSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-settings-update.component'; -import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; -import { MockComponent, MockProvider } from 'ng-mocks'; -import { of } from 'rxjs'; -import { ButtonComponent } from 'app/shared/components/button.component'; -import { HttpResponse } from '@angular/common/http'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; -import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; -import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; -import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; -import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; - -function baseSettings() { - const mockTemplate = new IrisTemplate(); - mockTemplate.id = 1; - mockTemplate.content = 'Hello World'; - const mockChatSettings = new IrisChatSubSettings(); - mockChatSettings.id = 1; - mockChatSettings.template = mockTemplate; - mockChatSettings.enabled = true; - const mockHestiaSettings = new IrisHestiaSubSettings(); - mockHestiaSettings.id = 2; - mockHestiaSettings.template = mockTemplate; - mockHestiaSettings.enabled = true; - const mockCodeEditorSettings = new IrisCodeEditorSubSettings(); - mockCodeEditorSettings.id = 2; - mockCodeEditorSettings.enabled = false; - const irisSettings = new IrisGlobalSettings(); - irisSettings.id = 1; - irisSettings.irisChatSettings = mockChatSettings; - irisSettings.irisHestiaSettings = mockHestiaSettings; - irisSettings.irisCodeEditorSettings = mockCodeEditorSettings; - return irisSettings; -} - -function models() { - return [ - { - id: '1', - name: 'Model 1', - description: 'Model 1 Description', - }, - { - id: '2', - name: 'Model 2', - description: 'Model 2 Description', - }, - ] as IrisModel[]; -} - -describe('IrisSettingsUpdateComponent Component', () => { - let comp: IrisSettingsUpdateComponent; - let fixture: ComponentFixture; - let irisSettingsService: IrisSettingsService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [ - IrisSettingsUpdateComponent, - MockComponent(IrisCommonSubSettingsUpdateComponent), - MockComponent(IrisChatSubSettingsUpdateComponent), - MockComponent(IrisHestiaSubSettingsUpdateComponent), - MockComponent(IrisGlobalAutoupdateSettingsUpdateComponent), - MockComponent(ButtonComponent), - ], - providers: [MockProvider(IrisSettingsService)], - }) - .compileComponents() - .then(() => { - irisSettingsService = TestBed.inject(IrisSettingsService); - }); - fixture = TestBed.createComponent(IrisSettingsUpdateComponent); - comp = fixture.componentInstance; - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('Loads global settings correctly', () => { - const irisSettings = baseSettings(); - const getSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); - const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - comp.settingsType = IrisSettingsType.GLOBAL; - fixture.detectChanges(); - expect(getSettingsSpy).toHaveBeenCalledOnce(); - expect(getModelsSpy).toHaveBeenCalledOnce(); - expect(comp.irisSettings).toEqual(irisSettings); - expect(fixture.debugElement.nativeElement.querySelector('#inheritHestia')).toBeFalsy(); - }); - - it('Loads course settings correctly', () => { - const irisSettings = baseSettings(); - const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedCourseSettings').mockReturnValue(of(irisSettings)); - const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - const getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); - comp.settingsType = IrisSettingsType.COURSE; - comp.courseId = 1; - fixture.detectChanges(); - expect(getSettingsSpy).toHaveBeenCalledWith(1); - expect(getModelsSpy).toHaveBeenCalledOnce(); - expect(getParentSettingsSpy).toHaveBeenCalledOnce(); - expect(comp.irisSettings).toEqual(irisSettings); - }); - - it('Loads programming exercise settings correctly', () => { - const irisSettings = baseSettings(); - const getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedProgrammingExerciseSettings').mockReturnValue(of(irisSettings)); - const getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); - const getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getCombinedCourseSettings').mockReturnValue(of(irisSettings)); - comp.settingsType = IrisSettingsType.EXERCISE; - comp.exerciseId = 1; - fixture.detectChanges(); - expect(getSettingsSpy).toHaveBeenCalledWith(1); - expect(getModelsSpy).toHaveBeenCalledOnce(); - expect(getParentSettingsSpy).toHaveBeenCalledOnce(); - expect(comp.irisSettings).toEqual(irisSettings); - }); - - it('Saves global settings correctly', () => { - const irisSettings = baseSettings(); - irisSettings.id = undefined; - const irisSettingsSaved = baseSettings(); - const setSettingsSpy = jest.spyOn(irisSettingsService, 'setGlobalSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingsType = IrisSettingsType.GLOBAL; - comp.irisSettings = irisSettings; - comp.saveIrisSettings(); - expect(setSettingsSpy).toHaveBeenCalledWith(irisSettings); - expect(comp.irisSettings).toEqual(irisSettingsSaved); - }); - - it('Saves course settings correctly', () => { - const irisSettings = baseSettings(); - irisSettings.id = undefined; - const irisSettingsSaved = baseSettings(); - const setSettingsSpy = jest.spyOn(irisSettingsService, 'setCourseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingsType = IrisSettingsType.COURSE; - comp.courseId = 1; - comp.irisSettings = irisSettings; - comp.saveIrisSettings(); - expect(setSettingsSpy).toHaveBeenCalledWith(1, irisSettings); - expect(comp.irisSettings).toEqual(irisSettingsSaved); - }); - - it('Saves programming exercise settings correctly', () => { - const irisSettings = baseSettings(); - irisSettings.id = undefined; - const irisSettingsSaved = baseSettings(); - const setSettingsSpy = jest.spyOn(irisSettingsService, 'setProgrammingExerciseSettings').mockReturnValue(of(new HttpResponse({ body: irisSettingsSaved }))); - comp.settingsType = IrisSettingsType.EXERCISE; - comp.exerciseId = 1; - comp.irisSettings = irisSettings; - comp.saveIrisSettings(); - expect(setSettingsSpy).toHaveBeenCalledWith(1, irisSettings); - expect(comp.irisSettings).toEqual(irisSettingsSaved); - }); -}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts index 7d1110f8ee77..4a181b852a84 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-settings.service.spec.ts @@ -2,32 +2,7 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { take } from 'rxjs/operators'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; -import { IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; -import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; -import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; - -function mockSettings() { - const mockTemplate = new IrisTemplate(); - mockTemplate.id = 1; - mockTemplate.content = 'Hello World'; - const mockChatSettings = new IrisChatSubSettings(); - mockChatSettings.id = 1; - mockChatSettings.template = mockTemplate; - mockChatSettings.enabled = true; - const mockHestiaSettings = new IrisHestiaSubSettings(); - mockHestiaSettings.id = 2; - mockHestiaSettings.template = mockTemplate; - mockHestiaSettings.enabled = true; - const mockCodeEditorSettings = new IrisCodeEditorSubSettings(); - mockCodeEditorSettings.id = 2; - mockCodeEditorSettings.enabled = false; - const irisSettings = new IrisGlobalSettings(); - irisSettings.id = 1; - irisSettings.irisChatSettings = mockChatSettings; - irisSettings.irisHestiaSettings = mockHestiaSettings; - irisSettings.irisCodeEditorSettings = mockCodeEditorSettings; - return irisSettings; -} +import { mockSettings } from './mock-settings'; describe('Iris Settings Service', () => { let service: IrisSettingsService; diff --git a/src/test/javascript/spec/component/iris/settings/mock-settings.ts b/src/test/javascript/spec/component/iris/settings/mock-settings.ts new file mode 100644 index 000000000000..59e835dab7b4 --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/mock-settings.ts @@ -0,0 +1,42 @@ +import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisChatSubSettings, IrisCodeEditorSubSettings, IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; + +export function mockSettings() { + const mockTemplate = new IrisTemplate(); + mockTemplate.id = 1; + mockTemplate.content = 'Hello World'; + const mockChatSettings = new IrisChatSubSettings(); + mockChatSettings.id = 1; + mockChatSettings.template = mockTemplate; + mockChatSettings.enabled = true; + const mockHestiaSettings = new IrisHestiaSubSettings(); + mockHestiaSettings.id = 2; + mockHestiaSettings.template = mockTemplate; + mockHestiaSettings.enabled = true; + const mockCodeEditorSettings = new IrisCodeEditorSubSettings(); + mockCodeEditorSettings.id = 2; + mockCodeEditorSettings.enabled = false; + const irisSettings = new IrisGlobalSettings(); + irisSettings.id = 1; + irisSettings.irisChatSettings = mockChatSettings; + irisSettings.irisHestiaSettings = mockHestiaSettings; + irisSettings.irisCodeEditorSettings = mockCodeEditorSettings; + return irisSettings; +} + +export function models() { + return [ + { + id: '1', + name: 'Model 1', + description: 'Model 1 Description', + }, + { + id: '2', + name: 'Model 2', + description: 'Model 2 Description', + }, + ] as IrisModel[]; +} From 571ecfba832a4ccacb16cc68adc53bba694ddd7d Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 03:32:57 +0100 Subject: [PATCH 19/30] Add code editor sub settings in UI --- src/main/webapp/app/iris/iris.module.ts | 2 + ...-editor-sub-settings-update.component.html | 63 +++++++++ ...de-editor-sub-settings-update.component.ts | 133 ++++++++++++++++++ .../iris-settings-update.component.html | 8 +- src/main/webapp/i18n/de/iris.json | 12 +- src/main/webapp/i18n/en/iris.json | 12 +- 6 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.html create mode 100644 src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index d603bfb6cee2..f31ed834cbed 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -19,6 +19,7 @@ import { IrisLogoComponent } from './iris-logo/iris-logo.component'; import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from './settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; +import { IrisCodeEditorSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component'; @NgModule({ declarations: [ @@ -34,6 +35,7 @@ import { IrisGlobalAutoupdateSettingsUpdateComponent } from './settings/iris-set IrisChatSubSettingsUpdateComponent, IrisHestiaSubSettingsUpdateComponent, IrisGlobalAutoupdateSettingsUpdateComponent, + IrisCodeEditorSubSettingsUpdateComponent, ], imports: [CommonModule, MatDialogModule, FormsModule, FontAwesomeModule, ArtemisSharedModule, ArtemisMarkdownModule, ArtemisSharedComponentModule, RouterModule], providers: [], diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.html new file mode 100644 index 000000000000..eaadb2bab643 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.html @@ -0,0 +1,63 @@ +
+

Templates

+
+ + +
+ +
+

Chat Template

+ +
+
+

Problem Statement Generation Template

+ +
+
+

Template Repo Generation Template

+ +
+
+

Solution Repo Generation Template

+ +
+
+

Test Repo Generation Template

+ +
+
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts new file mode 100644 index 000000000000..48157ed79ea7 --- /dev/null +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts @@ -0,0 +1,133 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisCodeEditorSubSettings, IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; + +@Component({ + selector: 'jhi-iris-code-editor-sub-settings-update', + templateUrl: './iris-code-editor-sub-settings-update.component.html', +}) +export class IrisCodeEditorSubSettingsUpdateComponent implements OnInit, OnChanges { + @Input() + subSettings?: IrisCodeEditorSubSettings; + + @Input() + parentSubSettings?: IrisCodeEditorSubSettings; + + @Output() + onChanges = new EventEmitter(); + + previousTemplate?: IrisTemplate; + + chatTemplateContent: string; + problemStatementGenerationTemplateContent: string; + templateRepoGenerationTemplateContent: string; + solutionRepoGenerationTemplateContent: string; + testRepoGenerationTemplateContent: string; + + ngOnInit(): void { + this.resetTemplates(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.subSettings || changes.parentSubSettings) { + this.resetTemplates(); + } + } + + private resetTemplates() { + this.chatTemplateContent = this.subSettings?.chatTemplate?.content ?? this.parentSubSettings?.chatTemplate?.content ?? ''; + this.problemStatementGenerationTemplateContent = + this.subSettings?.problemStatementGenerationTemplate?.content ?? this.parentSubSettings?.problemStatementGenerationTemplate?.content ?? ''; + this.templateRepoGenerationTemplateContent = + this.subSettings?.templateRepoGenerationTemplate?.content ?? this.parentSubSettings?.templateRepoGenerationTemplate?.content ?? ''; + this.solutionRepoGenerationTemplateContent = + this.subSettings?.solutionRepoGenerationTemplate?.content ?? this.parentSubSettings?.solutionRepoGenerationTemplate?.content ?? ''; + this.testRepoGenerationTemplateContent = this.subSettings?.testRepoGenerationTemplate?.content ?? this.parentSubSettings?.testRepoGenerationTemplate?.content ?? ''; + } + + onInheritTemplateChanged() { + if (this.subSettings?.chatTemplate) { + this.previousTemplate = this.subSettings?.chatTemplate; + this.subSettings.chatTemplate = undefined; + this.chatTemplateContent = this.parentSubSettings?.chatTemplate?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.chatTemplate = this.previousTemplate ?? irisTemplate; + } + if (this.subSettings?.problemStatementGenerationTemplate) { + this.previousTemplate = this.subSettings?.problemStatementGenerationTemplate; + this.subSettings.problemStatementGenerationTemplate = undefined; + this.problemStatementGenerationTemplateContent = this.parentSubSettings?.problemStatementGenerationTemplate?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.problemStatementGenerationTemplate = this.previousTemplate ?? irisTemplate; + } + if (this.subSettings?.templateRepoGenerationTemplate) { + this.previousTemplate = this.subSettings?.templateRepoGenerationTemplate; + this.subSettings.templateRepoGenerationTemplate = undefined; + this.templateRepoGenerationTemplateContent = this.parentSubSettings?.templateRepoGenerationTemplate?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.templateRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + } + if (this.subSettings?.solutionRepoGenerationTemplate) { + this.previousTemplate = this.subSettings?.solutionRepoGenerationTemplate; + this.subSettings.solutionRepoGenerationTemplate = undefined; + this.solutionRepoGenerationTemplateContent = this.parentSubSettings?.solutionRepoGenerationTemplate?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.solutionRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + } + if (this.subSettings?.testRepoGenerationTemplate) { + this.previousTemplate = this.subSettings?.testRepoGenerationTemplate; + this.subSettings.testRepoGenerationTemplate = undefined; + this.testRepoGenerationTemplateContent = this.parentSubSettings?.testRepoGenerationTemplate?.content ?? ''; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = ''; + this.subSettings!.testRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + } + } + + onTemplateChanged() { + if (this.subSettings?.chatTemplate) { + this.subSettings.chatTemplate.content = this.chatTemplateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.chatTemplateContent; + this.subSettings!.chatTemplate = irisTemplate; + } + if (this.subSettings?.problemStatementGenerationTemplate) { + this.subSettings.problemStatementGenerationTemplate.content = this.problemStatementGenerationTemplateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.problemStatementGenerationTemplateContent; + this.subSettings!.problemStatementGenerationTemplate = irisTemplate; + } + if (this.subSettings?.templateRepoGenerationTemplate) { + this.subSettings.templateRepoGenerationTemplate.content = this.templateRepoGenerationTemplateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.templateRepoGenerationTemplateContent; + this.subSettings!.templateRepoGenerationTemplate = irisTemplate; + } + if (this.subSettings?.solutionRepoGenerationTemplate) { + this.subSettings.solutionRepoGenerationTemplate.content = this.solutionRepoGenerationTemplateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.solutionRepoGenerationTemplateContent; + this.subSettings!.solutionRepoGenerationTemplate = irisTemplate; + } + if (this.subSettings?.testRepoGenerationTemplate) { + this.subSettings.testRepoGenerationTemplate.content = this.testRepoGenerationTemplateContent; + } else { + const irisTemplate = new IrisTemplate(); + irisTemplate.content = this.testRepoGenerationTemplateContent; + this.subSettings!.testRepoGenerationTemplate = irisTemplate; + } + } +} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html index c887b0197325..192b4e0511b9 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html @@ -44,7 +44,8 @@

Hestia Se >

-
+ +

Code Editor Settings

Code [settingsType]="settingsType" (onChanges)="isDirty = true" > +
diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index 4dd02d161f06..fe03d4c27f8d 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -41,8 +41,16 @@ "rateLimitTimeframeHoursTooltip": "Der Zeitraum, in welchem das Rate Limit angewendet wird, in Stunden.", "template": { "title": "Template", - "inherit": "Template erben", - "inheritedInfo": "Dieses Template ist von einem höherem Level geerbt. Ändere es hier, um es in diesem Level zu überschreiben." + "inherit": "Template erben" + }, + "codeEditor": { + "templates": "Templates", + "templatesInheritSwitch": "Templates erben", + "chatTemplate": "Chat Template", + "problemStatementTemplate": "Problem Statement Generations Template", + "templateRepoTemplate": "Vorlagen Repository Generations Template", + "solutionRepoTemplate": "Lösungs Repository Generations Template", + "testRepoTemplate": "Test Repository Generations Template" } }, "title": { diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index 685e48d57d1e..aa0f5680e336 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -41,8 +41,16 @@ "rateLimitTimeframeHoursTooltip": "The time period in which the rate limit applies in hours.", "template": { "title": "Template", - "inherit": "Inherit Template", - "inheritedInfo": "This template is inherited from higher level settings. Edit it here to overwrite it for this level." + "inherit": "Inherit Template" + }, + "codeEditor": { + "templates": "Templates", + "templatesInheritSwitch": "Inherit Templates", + "chatTemplate": "Chat Template", + "problemStatementTemplate": "Problem Statement Generation Template", + "templateRepoTemplate": "Template Repository Generation Template", + "solutionRepoTemplate": "Solution Repository Generation Template", + "testRepoTemplate": "Test Repository Generation Template" } }, "title": { From f9dd37fe7a876b64c7a213a3c10cd15412d14312 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 04:17:55 +0100 Subject: [PATCH 20/30] Fix client tests --- .../iris/settings/iris-course-settings-update.component.spec.ts | 2 +- .../iris/settings/iris-global-settings-update.component.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts index 8658e39a7b08..6fac4a99c936 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts @@ -75,7 +75,7 @@ describe('IrisCourseSettingsUpdateComponent Component', () => { expect(getParentSettingsSpy).toHaveBeenCalledOnce(); expect(fixture.debugElement.query(By.directive(IrisGlobalAutoupdateSettingsUpdateComponent))).toBeFalsy(); - expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(3); + expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(2); expect(fixture.debugElement.query(By.directive(IrisChatSubSettingsUpdateComponent))).toBeTruthy(); expect(fixture.debugElement.query(By.directive(IrisHestiaSubSettingsUpdateComponent))).toBeTruthy(); }); diff --git a/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts index c03572224d89..377399f55fed 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts @@ -62,7 +62,7 @@ describe('IrisGlobalSettingsUpdateComponent Component', () => { expect(getModelsSpy).toHaveBeenCalledOnce(); expect(fixture.debugElement.query(By.directive(IrisGlobalAutoupdateSettingsUpdateComponent))).toBeTruthy(); - expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(3); + expect(fixture.debugElement.queryAll(By.directive(IrisCommonSubSettingsUpdateComponent))).toHaveLength(2); expect(fixture.debugElement.query(By.directive(IrisChatSubSettingsUpdateComponent))).toBeTruthy(); expect(fixture.debugElement.query(By.directive(IrisHestiaSubSettingsUpdateComponent))).toBeTruthy(); }); From adae0fdf56595d4a95384efe3b70a3ef78abbaba Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 22:22:53 +0100 Subject: [PATCH 21/30] Improve client test coverage --- ...de-editor-sub-settings-update.component.ts | 26 +-- ...is-common-sub-settings-update.component.ts | 2 - ...is-hestia-sub-settings-update.component.ts | 10 +- ...hat-sub-settings-update.component.spec.ts} | 37 +++- ...itor-sub-settings-update.component.spec.ts | 159 ++++++++++++++++++ ...mmon-sub-settings-update.component.spec.ts | 154 +++++++++++++++++ ...s-course-settings-update.component.spec.ts | 4 +- ...exercise-settings-update.component.spec.ts | 4 +- ...s-global-settings-update.component.spec.ts | 4 +- ...stia-sub-settings-update.component.spec.ts | 111 ++++++++++++ .../component/iris/settings/mock-settings.ts | 2 +- 11 files changed, 490 insertions(+), 23 deletions(-) rename src/test/javascript/spec/component/iris/settings/{iris-sub-settings-update.component.spec.ts => iris-chat-sub-settings-update.component.spec.ts} (73%) create mode 100644 src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts create mode 100644 src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts create mode 100644 src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts index 48157ed79ea7..d35119e710a5 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component.ts @@ -16,7 +16,11 @@ export class IrisCodeEditorSubSettingsUpdateComponent implements OnInit, OnChang @Output() onChanges = new EventEmitter(); - previousTemplate?: IrisTemplate; + previousChatTemplate?: IrisTemplate; + previousProblemStatementGenerationTemplate?: IrisTemplate; + previousTemplateRepoGenerationTemplate?: IrisTemplate; + previousSolutionRepoGenerationTemplate?: IrisTemplate; + previousTestRepoGenerationTemplate?: IrisTemplate; chatTemplateContent: string; problemStatementGenerationTemplateContent: string; @@ -47,49 +51,49 @@ export class IrisCodeEditorSubSettingsUpdateComponent implements OnInit, OnChang onInheritTemplateChanged() { if (this.subSettings?.chatTemplate) { - this.previousTemplate = this.subSettings?.chatTemplate; + this.previousChatTemplate = this.subSettings?.chatTemplate; this.subSettings.chatTemplate = undefined; this.chatTemplateContent = this.parentSubSettings?.chatTemplate?.content ?? ''; } else { const irisTemplate = new IrisTemplate(); irisTemplate.content = ''; - this.subSettings!.chatTemplate = this.previousTemplate ?? irisTemplate; + this.subSettings!.chatTemplate = this.previousChatTemplate ?? irisTemplate; } if (this.subSettings?.problemStatementGenerationTemplate) { - this.previousTemplate = this.subSettings?.problemStatementGenerationTemplate; + this.previousProblemStatementGenerationTemplate = this.subSettings?.problemStatementGenerationTemplate; this.subSettings.problemStatementGenerationTemplate = undefined; this.problemStatementGenerationTemplateContent = this.parentSubSettings?.problemStatementGenerationTemplate?.content ?? ''; } else { const irisTemplate = new IrisTemplate(); irisTemplate.content = ''; - this.subSettings!.problemStatementGenerationTemplate = this.previousTemplate ?? irisTemplate; + this.subSettings!.problemStatementGenerationTemplate = this.previousProblemStatementGenerationTemplate ?? irisTemplate; } if (this.subSettings?.templateRepoGenerationTemplate) { - this.previousTemplate = this.subSettings?.templateRepoGenerationTemplate; + this.previousTemplateRepoGenerationTemplate = this.subSettings?.templateRepoGenerationTemplate; this.subSettings.templateRepoGenerationTemplate = undefined; this.templateRepoGenerationTemplateContent = this.parentSubSettings?.templateRepoGenerationTemplate?.content ?? ''; } else { const irisTemplate = new IrisTemplate(); irisTemplate.content = ''; - this.subSettings!.templateRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + this.subSettings!.templateRepoGenerationTemplate = this.previousTemplateRepoGenerationTemplate ?? irisTemplate; } if (this.subSettings?.solutionRepoGenerationTemplate) { - this.previousTemplate = this.subSettings?.solutionRepoGenerationTemplate; + this.previousSolutionRepoGenerationTemplate = this.subSettings?.solutionRepoGenerationTemplate; this.subSettings.solutionRepoGenerationTemplate = undefined; this.solutionRepoGenerationTemplateContent = this.parentSubSettings?.solutionRepoGenerationTemplate?.content ?? ''; } else { const irisTemplate = new IrisTemplate(); irisTemplate.content = ''; - this.subSettings!.solutionRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + this.subSettings!.solutionRepoGenerationTemplate = this.previousSolutionRepoGenerationTemplate ?? irisTemplate; } if (this.subSettings?.testRepoGenerationTemplate) { - this.previousTemplate = this.subSettings?.testRepoGenerationTemplate; + this.previousTestRepoGenerationTemplate = this.subSettings?.testRepoGenerationTemplate; this.subSettings.testRepoGenerationTemplate = undefined; this.testRepoGenerationTemplateContent = this.parentSubSettings?.testRepoGenerationTemplate?.content ?? ''; } else { const irisTemplate = new IrisTemplate(); irisTemplate.content = ''; - this.subSettings!.testRepoGenerationTemplate = this.previousTemplate ?? irisTemplate; + this.subSettings!.testRepoGenerationTemplate = this.previousTestRepoGenerationTemplate ?? irisTemplate; } } diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index 2de88118cc0a..ddbc03823834 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -92,11 +92,9 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { onInheritAllowedModelsChange() { if (this.inheritAllowedModels) { - this.inheritAllowedModels = true; this.subSettings!.allowedModels = undefined; this.allowedIrisModels = this.getAvailableModels(); } else { - this.inheritAllowedModels = false; this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); } } diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts index d2055c2ff6ac..ea7e98eac424 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; import { IrisHestiaSubSettings, IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; @@ -6,7 +6,7 @@ import { IrisHestiaSubSettings, IrisSubSettings } from 'app/entities/iris/settin selector: 'jhi-iris-hestia-sub-settings-update', templateUrl: './iris-hestia-sub-settings-update.component.html', }) -export class IrisHestiaSubSettingsUpdateComponent implements OnInit { +export class IrisHestiaSubSettingsUpdateComponent implements OnInit, OnChanges { @Input() subSettings: IrisHestiaSubSettings; @@ -26,6 +26,12 @@ export class IrisHestiaSubSettingsUpdateComponent implements OnInit { this.templateContent = this.subSettings.template?.content ?? this.parentSubSettings?.template?.content ?? ''; } + ngOnChanges(changes: SimpleChanges): void { + if (changes.subSettings || changes.parentSubSettings) { + this.templateContent = this.subSettings?.template?.content ?? this.parentSubSettings?.template?.content ?? ''; + } + } + onInheritTemplateChanged() { if (this.subSettings.template) { this.previousTemplate = this.subSettings.template; diff --git a/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts similarity index 73% rename from src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts rename to src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts index da6d60e70740..530d3a6a9547 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts @@ -6,6 +6,7 @@ import { IrisChatSubSettings } from 'app/entities/iris/settings/iris-sub-setting import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { MockDirective } from 'ng-mocks'; import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-chat-sub-settings-update/iris-chat-sub-settings-update.component'; +import { SimpleChange, SimpleChanges } from '@angular/core'; function baseSettings() { const mockTemplate = new IrisTemplate(); @@ -18,7 +19,7 @@ function baseSettings() { return irisSubSettings; } -describe('IrisSubSettingsUpdateComponent Component', () => { +describe('IrisChatSubSettingsUpdateComponent Component', () => { let comp: IrisChatSubSettingsUpdateComponent; let fixture: ComponentFixture; @@ -73,4 +74,38 @@ describe('IrisSubSettingsUpdateComponent Component', () => { expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); }); + + it('template changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + comp.templateContent = 'Hello World 2'; + comp.onTemplateChanged(); + + expect(comp.subSettings.template?.content).toBe('Hello World 2'); + }); + + it('template created', () => { + comp.subSettings = baseSettings(); + comp.subSettings.template = undefined; + fixture.detectChanges(); + comp.templateContent = 'Hello World 2'; + comp.onTemplateChanged(); + + expect(comp.subSettings.template?.content).toBe('Hello World 2'); + }); + + it('sub settings changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + const newSubSettings = baseSettings(); + newSubSettings.template!.content = 'Hello World 2'; + + const changes: SimpleChanges = { + subSettings: new SimpleChange(comp.subSettings, newSubSettings), + }; + comp.subSettings = newSubSettings; + comp.ngOnChanges(changes); + + expect(comp.templateContent).toBe('Hello World 2'); + }); }); diff --git a/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts new file mode 100644 index 000000000000..a7275e3272ab --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts @@ -0,0 +1,159 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisCodeEditorSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { MockDirective } from 'ng-mocks'; +import { SimpleChange, SimpleChanges } from '@angular/core'; +import { IrisCodeEditorSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component'; + +function mockTemplate(id: number) { + const mockTemplate = new IrisTemplate(); + mockTemplate.id = id; + mockTemplate.content = 'Hello World'; + return mockTemplate; +} + +function baseSettings() { + const irisSubSettings = new IrisCodeEditorSubSettings(); + irisSubSettings.id = 2; + irisSubSettings.chatTemplate = mockTemplate(1); + irisSubSettings.problemStatementGenerationTemplate = mockTemplate(2); + irisSubSettings.templateRepoGenerationTemplate = mockTemplate(3); + irisSubSettings.solutionRepoGenerationTemplate = mockTemplate(4); + irisSubSettings.testRepoGenerationTemplate = mockTemplate(5); + irisSubSettings.enabled = true; + return irisSubSettings; +} + +describe('IrisCodeEditorSubSettingsUpdateComponent Component', () => { + let comp: IrisCodeEditorSubSettingsUpdateComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, FormsModule, MockDirective(NgbTooltip)], + declarations: [IrisCodeEditorSubSettingsUpdateComponent], + }).compileComponents(); + fixture = TestBed.createComponent(IrisCodeEditorSubSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('template is not optional', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeFalsy(); + expect(fixture.debugElement.nativeElement.querySelector('#chat-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#problem-statement-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-repo-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#solution-repo-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#test-repo-template-editor')).toBeTruthy(); + }); + + it('template is optional', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#chat-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#problem-statement-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-repo-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#solution-repo-template-editor')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#test-repo-template-editor')).toBeTruthy(); + }); + + it('template is optional and changes from defined to undefined', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + comp.onInheritTemplateChanged(); + fixture.detectChanges(); + expect(comp.subSettings.chatTemplate).toBeUndefined(); + expect(comp.subSettings.problemStatementGenerationTemplate).toBeUndefined(); + expect(comp.subSettings.templateRepoGenerationTemplate).toBeUndefined(); + expect(comp.subSettings.solutionRepoGenerationTemplate).toBeUndefined(); + expect(comp.subSettings.testRepoGenerationTemplate).toBeUndefined(); + }); + + it('template is optional and changes from undefined to defined', () => { + const subSettings = baseSettings(); + subSettings.chatTemplate = undefined; + subSettings.problemStatementGenerationTemplate = undefined; + subSettings.templateRepoGenerationTemplate = undefined; + subSettings.solutionRepoGenerationTemplate = undefined; + subSettings.testRepoGenerationTemplate = undefined; + comp.subSettings = subSettings; + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + comp.onInheritTemplateChanged(); + fixture.detectChanges(); + expect(comp.subSettings.chatTemplate).toBeDefined(); + expect(comp.subSettings.problemStatementGenerationTemplate).toBeDefined(); + expect(comp.subSettings.templateRepoGenerationTemplate).toBeDefined(); + expect(comp.subSettings.solutionRepoGenerationTemplate).toBeDefined(); + expect(comp.subSettings.testRepoGenerationTemplate).toBeDefined(); + }); + + it('template changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + comp.chatTemplateContent = 'Hello World 2'; + comp.problemStatementGenerationTemplateContent = 'Hello World 3'; + comp.templateRepoGenerationTemplateContent = 'Hello World 4'; + comp.solutionRepoGenerationTemplateContent = 'Hello World 5'; + comp.testRepoGenerationTemplateContent = 'Hello World 6'; + comp.onTemplateChanged(); + + expect(comp.subSettings.chatTemplate?.content).toBe('Hello World 2'); + expect(comp.subSettings.problemStatementGenerationTemplate?.content).toBe('Hello World 3'); + expect(comp.subSettings.templateRepoGenerationTemplate?.content).toBe('Hello World 4'); + expect(comp.subSettings.solutionRepoGenerationTemplate?.content).toBe('Hello World 5'); + expect(comp.subSettings.testRepoGenerationTemplate?.content).toBe('Hello World 6'); + }); + + it('template created', () => { + comp.subSettings = baseSettings(); + comp.subSettings.chatTemplate = undefined; + fixture.detectChanges(); + comp.chatTemplateContent = 'Hello World 2'; + comp.problemStatementGenerationTemplateContent = 'Hello World 3'; + comp.templateRepoGenerationTemplateContent = 'Hello World 4'; + comp.solutionRepoGenerationTemplateContent = 'Hello World 5'; + comp.testRepoGenerationTemplateContent = 'Hello World 6'; + comp.onTemplateChanged(); + + expect(comp.subSettings.chatTemplate?.content).toBe('Hello World 2'); + expect(comp.subSettings.problemStatementGenerationTemplate?.content).toBe('Hello World 3'); + expect(comp.subSettings.templateRepoGenerationTemplate?.content).toBe('Hello World 4'); + expect(comp.subSettings.solutionRepoGenerationTemplate?.content).toBe('Hello World 5'); + expect(comp.subSettings.testRepoGenerationTemplate?.content).toBe('Hello World 6'); + }); + + it('sub settings changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + const newSubSettings = baseSettings(); + newSubSettings.chatTemplate!.content = 'Hello World 2'; + newSubSettings.problemStatementGenerationTemplate!.content = 'Hello World 3'; + newSubSettings.templateRepoGenerationTemplate!.content = 'Hello World 4'; + newSubSettings.solutionRepoGenerationTemplate!.content = 'Hello World 5'; + newSubSettings.testRepoGenerationTemplate!.content = 'Hello World 6'; + + const changes: SimpleChanges = { + subSettings: new SimpleChange(comp.subSettings, newSubSettings), + }; + comp.subSettings = newSubSettings; + comp.ngOnChanges(changes); + + expect(comp.chatTemplateContent).toBe('Hello World 2'); + expect(comp.problemStatementGenerationTemplateContent).toBe('Hello World 3'); + expect(comp.templateRepoGenerationTemplateContent).toBe('Hello World 4'); + expect(comp.solutionRepoGenerationTemplateContent).toBe('Hello World 5'); + expect(comp.testRepoGenerationTemplateContent).toBe('Hello World 6'); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts new file mode 100644 index 000000000000..ea2a69046716 --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts @@ -0,0 +1,154 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { IrisChatSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { MockDirective, MockPipe } from 'ng-mocks'; +import { SimpleChange, SimpleChanges } from '@angular/core'; +import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; +import { mockModels } from './mock-settings'; +import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; + +function baseSettings() { + const mockTemplate = new IrisTemplate(); + mockTemplate.id = 1; + mockTemplate.content = 'Hello World'; + const irisSubSettings = new IrisChatSubSettings(); + irisSubSettings.id = 2; + irisSubSettings.enabled = true; + const allowedModels = mockModels(); + allowedModels.pop(); + irisSubSettings.allowedModels = allowedModels.map((model) => model.id!); + irisSubSettings.preferredModel = allowedModels[0]; + return irisSubSettings; +} + +describe('IrisCommonSubSettingsUpdateComponent Component', () => { + let comp: IrisCommonSubSettingsUpdateComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, FormsModule, MockDirective(NgbTooltip), MockPipe(ArtemisTranslatePipe)], + declarations: [IrisCommonSubSettingsUpdateComponent], + }).compileComponents(); + fixture = TestBed.createComponent(IrisCommonSubSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('child setup works', () => { + const subSettings = baseSettings(); + comp.subSettings = subSettings; + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + expect(comp.enabled).toBeTrue(); + expect(comp.inheritAllowedModels).toBeFalse(); + expect(comp.allowedIrisModels).toEqual([subSettings.preferredModel]); + }); + + it('parent setup works', () => { + const subSettings = baseSettings(); + subSettings.allowedModels = undefined; + subSettings.preferredModel = undefined; + comp.subSettings = subSettings; + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + expect(comp.enabled).toBeTrue(); + expect(comp.inheritAllowedModels).toBeTrue(); + expect(comp.allowedIrisModels).toEqual([comp.parentSubSettings.preferredModel]); + }); + + it('change allowed model', () => { + const allIrisModels = mockModels(); + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = allIrisModels; + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + comp.onAllowedIrisModelsSelectionChange(allIrisModels[1]); + expect(comp.allowedIrisModels).toEqual([allIrisModels[0], allIrisModels[1]]); + comp.onAllowedIrisModelsSelectionChange(allIrisModels[0]); + expect(comp.allowedIrisModels).toEqual([allIrisModels[1]]); + }); + + it('change preferred model', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + comp.setModel(mockModels()[1]); + expect(comp.subSettings!.preferredModel).toBe(mockModels()[1].id); + }); + + it('change enabled', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + comp.enabled = false; + comp.onEnabledChange(); + expect(comp.subSettings!.enabled).toBeFalse(); + + comp.enabled = true; + comp.onEnabledChange(); + expect(comp.subSettings!.enabled).toBeTrue(); + }); + + it('change inherit allowed models', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + comp.inheritAllowedModels = true; + comp.onInheritAllowedModelsChange(); + expect(comp.subSettings!.allowedModels).toBeUndefined(); + expect(comp.allowedIrisModels).toEqual(comp.getAvailableModels()); + + comp.inheritAllowedModels = false; + comp.onInheritAllowedModelsChange(); + expect(comp.subSettings!.allowedModels).toEqual(comp.allowedIrisModels.map((model) => model.id)); + }); + + it('ngOnChanges works', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + comp.allIrisModels = mockModels(); + comp.settingsType = IrisSettingsType.EXERCISE; + fixture.detectChanges(); + + const newSubSettings = baseSettings(); + newSubSettings.enabled = false; + const newModels = mockModels(); + newModels.pop(); + + const changes: SimpleChanges = { + subSettings: new SimpleChange(comp.subSettings, newSubSettings), + allIrisModels: new SimpleChange(comp.allIrisModels, newModels), + }; + comp.subSettings = newSubSettings; + comp.allIrisModels = mockModels(); + comp.ngOnChanges(changes); + + expect(comp.enabled).toBeFalse(); + expect(comp.allowedIrisModels).toEqual(newModels); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts index 6fac4a99c936..19add1e5a5cc 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts @@ -9,7 +9,7 @@ import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-setti import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; -import { mockSettings, models } from './mock-settings'; +import { mockModels, mockSettings } from './mock-settings'; import { ActivatedRoute, Params } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { NgModel } from '@angular/forms'; @@ -54,7 +54,7 @@ describe('IrisCourseSettingsUpdateComponent Component', () => { const irisSettings = mockSettings(); getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedCourseSettings').mockReturnValue(of(irisSettings)); - getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(mockModels())); getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); }); fixture = TestBed.createComponent(IrisCourseSettingsUpdateComponent); diff --git a/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts index 66b00eeabee4..0de12c7c83e0 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts @@ -9,7 +9,7 @@ import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-setti import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; -import { mockSettings, models } from './mock-settings'; +import { mockModels, mockSettings } from './mock-settings'; import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component'; import { ActivatedRoute, Params } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -54,7 +54,7 @@ describe('IrisExerciseSettingsUpdateComponent Component', () => { const irisSettings = mockSettings(); getSettingsSpy = jest.spyOn(irisSettingsService, 'getUncombinedProgrammingExerciseSettings').mockReturnValue(of(irisSettings)); - getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(mockModels())); getParentSettingsSpy = jest.spyOn(irisSettingsService, 'getCombinedCourseSettings').mockReturnValue(of(irisSettings)); }); fixture = TestBed.createComponent(IrisExerciseSettingsUpdateComponent); diff --git a/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts index 377399f55fed..6cb5e4bd0c94 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-global-settings-update.component.spec.ts @@ -9,7 +9,7 @@ import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-setti import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; -import { mockSettings, models } from './mock-settings'; +import { mockModels, mockSettings } from './mock-settings'; import { NgModel } from '@angular/forms'; import { IrisGlobalSettingsUpdateComponent } from 'app/iris/settings/iris-global-settings-update/iris-global-settings-update.component'; import { By } from '@angular/platform-browser'; @@ -45,7 +45,7 @@ describe('IrisGlobalSettingsUpdateComponent Component', () => { // Setup const irisSettings = mockSettings(); getSettingsSpy = jest.spyOn(irisSettingsService, 'getGlobalSettings').mockReturnValue(of(irisSettings)); - getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(models())); + getModelsSpy = jest.spyOn(irisSettingsService, 'getIrisModels').mockReturnValue(of(mockModels())); }); fixture = TestBed.createComponent(IrisGlobalSettingsUpdateComponent); comp = fixture.componentInstance; diff --git a/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts new file mode 100644 index 000000000000..ad565133b19c --- /dev/null +++ b/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts @@ -0,0 +1,111 @@ +import { ArtemisTestModule } from '../../../test.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { MockDirective } from 'ng-mocks'; +import { SimpleChange, SimpleChanges } from '@angular/core'; +import { IrisHestiaSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; + +function baseSettings() { + const mockTemplate = new IrisTemplate(); + mockTemplate.id = 1; + mockTemplate.content = 'Hello World'; + const irisSubSettings = new IrisHestiaSubSettings(); + irisSubSettings.id = 2; + irisSubSettings.template = mockTemplate; + irisSubSettings.enabled = true; + return irisSubSettings; +} + +describe('IrisHestiaSubSettingsUpdateComponent Component', () => { + let comp: IrisHestiaSubSettingsUpdateComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, FormsModule, MockDirective(NgbTooltip)], + declarations: [IrisHestiaSubSettingsUpdateComponent], + }).compileComponents(); + fixture = TestBed.createComponent(IrisHestiaSubSettingsUpdateComponent); + comp = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('template is not optional', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeFalsy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); + }); + + it('template is optional', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); + }); + + it('template is optional and changes from defined to undefined', () => { + comp.subSettings = baseSettings(); + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + comp.onInheritTemplateChanged(); + fixture.detectChanges(); + expect(comp.subSettings.template).toBeUndefined(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); + }); + + it('template is optional and changes from undefined to defined', () => { + const subSettings = baseSettings(); + subSettings.template = undefined; + comp.subSettings = subSettings; + comp.parentSubSettings = baseSettings(); + fixture.detectChanges(); + comp.onInheritTemplateChanged(); + fixture.detectChanges(); + expect(comp.subSettings.template).toBeDefined(); + expect(fixture.debugElement.nativeElement.querySelector('#inheritTemplate')).toBeTruthy(); + expect(fixture.debugElement.nativeElement.querySelector('#template-editor')).toBeTruthy(); + }); + + it('template changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + comp.templateContent = 'Hello World 2'; + comp.onTemplateChanged(); + + expect(comp.subSettings.template?.content).toBe('Hello World 2'); + }); + + it('template created', () => { + comp.subSettings = baseSettings(); + comp.subSettings.template = undefined; + fixture.detectChanges(); + comp.templateContent = 'Hello World 2'; + comp.onTemplateChanged(); + + expect(comp.subSettings.template?.content).toBe('Hello World 2'); + }); + + it('sub settings changes', () => { + comp.subSettings = baseSettings(); + fixture.detectChanges(); + const newSubSettings = baseSettings(); + newSubSettings.template!.content = 'Hello World 2'; + + const changes: SimpleChanges = { + subSettings: new SimpleChange(comp.subSettings, newSubSettings), + }; + comp.subSettings = newSubSettings; + comp.ngOnChanges(changes); + + expect(comp.templateContent).toBe('Hello World 2'); + }); +}); diff --git a/src/test/javascript/spec/component/iris/settings/mock-settings.ts b/src/test/javascript/spec/component/iris/settings/mock-settings.ts index 59e835dab7b4..c25844a503d8 100644 --- a/src/test/javascript/spec/component/iris/settings/mock-settings.ts +++ b/src/test/javascript/spec/component/iris/settings/mock-settings.ts @@ -26,7 +26,7 @@ export function mockSettings() { return irisSettings; } -export function models() { +export function mockModels() { return [ { id: '1', From f980e83bbe919df5f7311dc6a9aa89f483877c0b Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 22:38:56 +0100 Subject: [PATCH 22/30] Fix client tests --- .../iris-chat-sub-settings-update.component.spec.ts | 4 ++-- .../iris-code-editor-sub-settings-update.component.spec.ts | 2 +- .../iris-common-sub-settings-update.component.spec.ts | 6 +++--- .../iris-hestia-sub-settings-update.component.spec.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts index 530d3a6a9547..e738583f2b4f 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-chat-sub-settings-update.component.spec.ts @@ -91,7 +91,7 @@ describe('IrisChatSubSettingsUpdateComponent Component', () => { comp.templateContent = 'Hello World 2'; comp.onTemplateChanged(); - expect(comp.subSettings.template?.content).toBe('Hello World 2'); + expect(comp.subSettings.template!.content).toBe('Hello World 2'); }); it('sub settings changes', () => { @@ -101,7 +101,7 @@ describe('IrisChatSubSettingsUpdateComponent Component', () => { newSubSettings.template!.content = 'Hello World 2'; const changes: SimpleChanges = { - subSettings: new SimpleChange(comp.subSettings, newSubSettings), + subSettings: new SimpleChange(comp.subSettings, newSubSettings, false), }; comp.subSettings = newSubSettings; comp.ngOnChanges(changes); diff --git a/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts index a7275e3272ab..079a8f4a28fa 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts @@ -145,7 +145,7 @@ describe('IrisCodeEditorSubSettingsUpdateComponent Component', () => { newSubSettings.testRepoGenerationTemplate!.content = 'Hello World 6'; const changes: SimpleChanges = { - subSettings: new SimpleChange(comp.subSettings, newSubSettings), + subSettings: new SimpleChange(comp.subSettings, newSubSettings, false), }; comp.subSettings = newSubSettings; comp.ngOnChanges(changes); diff --git a/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts index ea2a69046716..70030b2632d9 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts @@ -21,7 +21,7 @@ function baseSettings() { const allowedModels = mockModels(); allowedModels.pop(); irisSubSettings.allowedModels = allowedModels.map((model) => model.id!); - irisSubSettings.preferredModel = allowedModels[0]; + irisSubSettings.preferredModel = allowedModels[0].id!; return irisSubSettings; } @@ -141,8 +141,8 @@ describe('IrisCommonSubSettingsUpdateComponent Component', () => { newModels.pop(); const changes: SimpleChanges = { - subSettings: new SimpleChange(comp.subSettings, newSubSettings), - allIrisModels: new SimpleChange(comp.allIrisModels, newModels), + subSettings: new SimpleChange(comp.subSettings, newSubSettings, false), + allIrisModels: new SimpleChange(comp.allIrisModels, newModels, false), }; comp.subSettings = newSubSettings; comp.allIrisModels = mockModels(); diff --git a/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts index ad565133b19c..4941c6c92dbb 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-hestia-sub-settings-update.component.spec.ts @@ -91,7 +91,7 @@ describe('IrisHestiaSubSettingsUpdateComponent Component', () => { comp.templateContent = 'Hello World 2'; comp.onTemplateChanged(); - expect(comp.subSettings.template?.content).toBe('Hello World 2'); + expect(comp.subSettings.template!.content).toBe('Hello World 2'); }); it('sub settings changes', () => { @@ -101,7 +101,7 @@ describe('IrisHestiaSubSettingsUpdateComponent Component', () => { newSubSettings.template!.content = 'Hello World 2'; const changes: SimpleChanges = { - subSettings: new SimpleChange(comp.subSettings, newSubSettings), + subSettings: new SimpleChange(comp.subSettings, newSubSettings, false), }; comp.subSettings = newSubSettings; comp.ngOnChanges(changes); From 23d4bdb467e8fb1ec2b633c946c993ad54c82972 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 22:43:49 +0100 Subject: [PATCH 23/30] Fix client tests II --- ...s-code-editor-sub-settings-update.component.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts index 079a8f4a28fa..ec9f684e64ce 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-code-editor-sub-settings-update.component.spec.ts @@ -127,11 +127,11 @@ describe('IrisCodeEditorSubSettingsUpdateComponent Component', () => { comp.testRepoGenerationTemplateContent = 'Hello World 6'; comp.onTemplateChanged(); - expect(comp.subSettings.chatTemplate?.content).toBe('Hello World 2'); - expect(comp.subSettings.problemStatementGenerationTemplate?.content).toBe('Hello World 3'); - expect(comp.subSettings.templateRepoGenerationTemplate?.content).toBe('Hello World 4'); - expect(comp.subSettings.solutionRepoGenerationTemplate?.content).toBe('Hello World 5'); - expect(comp.subSettings.testRepoGenerationTemplate?.content).toBe('Hello World 6'); + expect(comp.subSettings.chatTemplate!.content).toBe('Hello World 2'); + expect(comp.subSettings.problemStatementGenerationTemplate!.content).toBe('Hello World 3'); + expect(comp.subSettings.templateRepoGenerationTemplate!.content).toBe('Hello World 4'); + expect(comp.subSettings.solutionRepoGenerationTemplate!.content).toBe('Hello World 5'); + expect(comp.subSettings.testRepoGenerationTemplate!.content).toBe('Hello World 6'); }); it('sub settings changes', () => { From 67db37921f4c51da640fba2f2ec0edea4b32b1c7 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 5 Nov 2023 23:12:31 +0100 Subject: [PATCH 24/30] Fix client tests III --- .../iris-common-sub-settings-update.component.spec.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts index 70030b2632d9..ebb97b0be791 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-common-sub-settings-update.component.spec.ts @@ -43,8 +43,7 @@ describe('IrisCommonSubSettingsUpdateComponent Component', () => { }); it('child setup works', () => { - const subSettings = baseSettings(); - comp.subSettings = subSettings; + comp.subSettings = baseSettings(); comp.parentSubSettings = baseSettings(); comp.allIrisModels = mockModels(); comp.settingsType = IrisSettingsType.EXERCISE; @@ -52,7 +51,7 @@ describe('IrisCommonSubSettingsUpdateComponent Component', () => { expect(comp.enabled).toBeTrue(); expect(comp.inheritAllowedModels).toBeFalse(); - expect(comp.allowedIrisModels).toEqual([subSettings.preferredModel]); + expect(comp.allowedIrisModels).toEqual([mockModels()[0]]); }); it('parent setup works', () => { @@ -67,7 +66,7 @@ describe('IrisCommonSubSettingsUpdateComponent Component', () => { expect(comp.enabled).toBeTrue(); expect(comp.inheritAllowedModels).toBeTrue(); - expect(comp.allowedIrisModels).toEqual([comp.parentSubSettings.preferredModel]); + expect(comp.allowedIrisModels).toEqual([mockModels()[0]]); }); it('change allowed model', () => { From f73d45faf6f94b80d002151f603b86e2e3fa758f Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Mon, 6 Nov 2023 13:55:52 +0100 Subject: [PATCH 25/30] Work around weird Angular stuff --- ...-common-sub-settings-update.component.html | 30 ++++++++----------- ...is-common-sub-settings-update.component.ts | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html index 5b37336d4c1b..b8288beb95fa 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html @@ -23,23 +23,19 @@

Inherit

-
-
-
-
- - -
-
+
+
+ +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index ddbc03823834..2ad50a459bb3 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -48,7 +48,7 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { ngOnInit() { this.enabled = this.subSettings?.enabled ?? false; this.allowedIrisModels = this.getAvailableModels(); - this.inheritAllowedModels = !this.subSettings?.allowedModels && this.parentSubSettings !== undefined; + this.inheritAllowedModels = (!this.subSettings?.allowedModels && this.parentSubSettings) as boolean; } ngOnChanges(changes: SimpleChanges): void { From a46bd9d37dbda98b4fea987466909bc261d09dad Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 12 Nov 2023 22:42:32 +0100 Subject: [PATCH 26/30] 2 small fixes --- .../service/iris/settings/IrisSubSettingsService.java | 8 ++++++++ .../iris-common-sub-settings-update.component.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index 7fea3e008657..b5bd921e697e 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -287,6 +287,14 @@ private Integer getCombinedRateLimit(List settingsList) { .filter(rateLimit -> rateLimit != null && rateLimit >= 0).min(Comparator.comparingInt(Integer::intValue)).orElse(null); } + /** + * Combines the allowedModels field of multiple {@link IrisSettings} objects. + * Simply takes the last allowedModels. + * + * @param settingsList List of {@link IrisSettings} objects to combine. + * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. + * @return Combined allowedModels field. + */ private Set getCombinedAllowedModels(List settingsList, Function subSettingsFunction) { return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getAllowedModels).filter(Objects::nonNull) .filter(models -> !models.isEmpty()).reduce((first, second) -> second).orElse(new TreeSet<>()); diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index 2ad50a459bb3..fc382c8f8cc0 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -48,7 +48,7 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { ngOnInit() { this.enabled = this.subSettings?.enabled ?? false; this.allowedIrisModels = this.getAvailableModels(); - this.inheritAllowedModels = (!this.subSettings?.allowedModels && this.parentSubSettings) as boolean; + this.inheritAllowedModels = !!(!this.subSettings?.allowedModels && this.parentSubSettings); } ngOnChanges(changes: SimpleChanges): void { From 953bd07f07a2fd86b6a02710f183932e3cd6098f Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Sun, 12 Nov 2023 23:31:56 +0100 Subject: [PATCH 27/30] 2 small UI fixes --- .../iris-settings-update.component.html | 9 ++++++++- .../iris-settings-update.component.ts | 15 ++++++++++----- .../app/shared/layouts/navbar/navbar.component.ts | 1 + src/main/webapp/i18n/de/iris.json | 1 + src/main/webapp/i18n/en/iris.json | 1 + 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html index 192b4e0511b9..c9f0bcd5ddc0 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html @@ -1,5 +1,12 @@
- +
diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 1771ed526c1d..288b89afeb2a 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, DoCheck, Input, OnInit } from '@angular/core'; import { IrisSettings, IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; import { HttpResponse } from '@angular/common/http'; @@ -8,12 +8,13 @@ import { ButtonType } from 'app/shared/components/button.component'; import { faRotate, faSave } from '@fortawesome/free-solid-svg-icons'; import { IrisModel } from 'app/entities/iris/settings/iris-model'; import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; +import { cloneDeep, isEqual } from 'lodash-es'; @Component({ selector: 'jhi-iris-settings-update', templateUrl: './iris-settings-update.component.html', }) -export class IrisSettingsUpdateComponent implements OnInit, OnChanges, ComponentCanDeactivate { +export class IrisSettingsUpdateComponent implements OnInit, DoCheck, ComponentCanDeactivate { @Input() public settingsType: IrisSettingsType; @Input() @@ -25,12 +26,15 @@ export class IrisSettingsUpdateComponent implements OnInit, OnChanges, Component public parentIrisSettings?: IrisSettings; public allIrisModels?: IrisModel[]; + originalIrisSettings?: IrisSettings; + // Status bools isLoading = false; isSaving = false; isDirty = false; // Button types PRIMARY = ButtonType.PRIMARY; + WARNING = ButtonType.WARNING; SUCCESS = ButtonType.SUCCESS; // Icons faSave = faSave; @@ -49,10 +53,9 @@ export class IrisSettingsUpdateComponent implements OnInit, OnChanges, Component this.loadIrisSettings(); } - ngOnChanges(changes: SimpleChanges): void { - if (changes.irisSettings && changes.irisSettings.previousValue) { + ngDoCheck(): void { + if (!isEqual(this.irisSettings, this.originalIrisSettings)) { this.isDirty = true; - console.log('dirty'); } } @@ -77,6 +80,7 @@ export class IrisSettingsUpdateComponent implements OnInit, OnChanges, Component this.alertService.error('artemisApp.iris.settings.error.noSettings'); } this.irisSettings = settings; + this.originalIrisSettings = cloneDeep(settings); this.isDirty = false; }); this.loadParentIrisSettingsObservable().subscribe((settings) => { @@ -94,6 +98,7 @@ export class IrisSettingsUpdateComponent implements OnInit, OnChanges, Component this.isSaving = false; this.isDirty = false; this.irisSettings = response.body ?? undefined; + this.originalIrisSettings = cloneDeep(this.irisSettings); this.alertService.success('artemisApp.iris.settings.success'); }, () => { diff --git a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts index 5a8446607dd6..b0c848d9cfb6 100644 --- a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts +++ b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts @@ -335,6 +335,7 @@ export class NavbarComponent implements OnInit, OnDestroy { suspicious_behavior: 'artemisApp.examManagement.suspiciousBehavior.title', suspicious_sessions: 'artemisApp.examManagement.suspiciousBehavior.suspiciousSessions.title', exam_timeline: 'artemisApp.examTimeline.breadcrumb', + iris_settings: 'artemisApp.iris.settings.title.breadcrumb', }; studentPathBreadcrumbTranslations = { diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index fe03d4c27f8d..2e5e6d2ae9e8 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -54,6 +54,7 @@ } }, "title": { + "breadcrumb": "Iris Einstellungen", "global": "Globale Iris Einstellungen", "course": "Kurs Iris Einstellungen", "programmingExercise": "Programmieraufgabe Iris Einstellungen" diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index aa0f5680e336..17551ebb3738 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -54,6 +54,7 @@ } }, "title": { + "breadcrumb": "Iris Settings", "global": "Global Iris Settings", "course": "Course Iris Settings", "exercise": "Exercise Iris Settings" From 357815852c4acaaae080ee4e593eab647ca34eff Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Mon, 20 Nov 2023 15:24:54 +0100 Subject: [PATCH 28/30] Fix deletion of courses and exercises that have Iris settings defined --- .../www1/artemis/service/CourseService.java | 8 ++++++- .../iris/settings/IrisSettingsService.java | 22 +++++++++++++++++++ .../ProgrammingExerciseService.java | 8 ++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java index 83afb219f85a..589d5edacff3 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java @@ -56,6 +56,7 @@ import de.tum.in.www1.artemis.service.dto.StudentDTO; import de.tum.in.www1.artemis.service.exam.ExamDeletionService; import de.tum.in.www1.artemis.service.export.CourseExamExportService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.service.learningpath.LearningPathService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationService; import de.tum.in.www1.artemis.service.tutorialgroups.TutorialGroupService; @@ -154,6 +155,8 @@ public class CourseService { private final LearningPathService learningPathService; + private final IrisSettingsService irisSettingsService; + public CourseService(Environment env, ArtemisAuthenticationProvider artemisAuthenticationProvider, CourseRepository courseRepository, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService, AuthorizationCheckService authCheckService, UserRepository userRepository, LectureService lectureService, GroupNotificationRepository groupNotificationRepository, ExerciseGroupRepository exerciseGroupRepository, AuditEventRepository auditEventRepository, @@ -164,7 +167,8 @@ public CourseService(Environment env, ArtemisAuthenticationProvider artemisAuthe ComplaintResponseRepository complaintResponseRepository, SubmissionRepository submissionRepository, ProgrammingExerciseRepository programmingExerciseRepository, ExerciseRepository exerciseRepository, ParticipantScoreRepository participantScoreRepository, PresentationPointsCalculationService presentationPointsCalculationService, TutorialGroupRepository tutorialGroupRepository, TutorialGroupService tutorialGroupService, TutorialGroupsConfigurationRepository tutorialGroupsConfigurationRepository, - PlagiarismCaseRepository plagiarismCaseRepository, ConversationRepository conversationRepository, LearningPathService learningPathService) { + PlagiarismCaseRepository plagiarismCaseRepository, ConversationRepository conversationRepository, LearningPathService learningPathService, + IrisSettingsService irisSettingsService) { this.env = env; this.artemisAuthenticationProvider = artemisAuthenticationProvider; this.courseRepository = courseRepository; @@ -203,6 +207,7 @@ public CourseService(Environment env, ArtemisAuthenticationProvider artemisAuthe this.plagiarismCaseRepository = plagiarismCaseRepository; this.conversationRepository = conversationRepository; this.learningPathService = learningPathService; + this.irisSettingsService = irisSettingsService; } /** @@ -373,6 +378,7 @@ public void delete(Course course) { deleteExamsOfCourse(course); deleteGradingScaleOfCourse(course); deleteTutorialGroupsOfCourse(course); + irisSettingsService.deleteSettingsFor(course); courseRepository.deleteById(course.getId()); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java index d468cabfb1cf..4c3b9d3c1632 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSettingsService.java @@ -386,4 +386,26 @@ public IrisCourseSettings getRawIrisSettingsFor(Course course) { public IrisExerciseSettings getRawIrisSettingsFor(Exercise exercise) { return irisSettingsRepository.findExerciseSettings(exercise.getId()).orElse(getDefaultSettingsFor(exercise)); } + + /** + * Delete the Iris settings for a course. + * If no Iris settings for the course exist, nothing happens. + * + * @param course The course to delete the Iris settings for + */ + public void deleteSettingsFor(Course course) { + var irisCourseSettingsOptional = irisSettingsRepository.findCourseSettings(course.getId()); + irisCourseSettingsOptional.ifPresent(irisSettingsRepository::delete); + } + + /** + * Delete the Iris settings for an exercise. + * If no Iris settings for the exercise exist, nothing happens. + * + * @param exercise The course to delete the Iris settings for + */ + public void deleteSettingsFor(Exercise exercise) { + var irisExerciseSettingsOptional = irisSettingsRepository.findExerciseSettings(exercise.getId()); + irisExerciseSettingsOptional.ifPresent(irisSettingsRepository::delete); + } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java index abafe2c11a01..5c7a6029bcb4 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java @@ -44,6 +44,7 @@ import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationTriggerService; import de.tum.in.www1.artemis.service.connectors.vcs.VersionControlService; import de.tum.in.www1.artemis.service.hestia.ProgrammingExerciseTaskService; +import de.tum.in.www1.artemis.service.iris.settings.IrisSettingsService; import de.tum.in.www1.artemis.service.messaging.InstanceMessageSendService; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; @@ -130,6 +131,8 @@ public class ProgrammingExerciseService { private final ProgrammingSubmissionService programmingSubmissionService; + private final IrisSettingsService irisSettingsService; + public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerciseRepository, GitService gitService, Optional versionControlService, Optional continuousIntegrationService, Optional continuousIntegrationTriggerService, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository, @@ -141,7 +144,7 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository, ExerciseSpecificationService exerciseSpecificationService, ProgrammingExerciseRepositoryService programmingExerciseRepositoryService, AuxiliaryRepositoryService auxiliaryRepositoryService, SubmissionPolicyService submissionPolicyService, Optional programmingLanguageFeatureService, ChannelService channelService, - ProgrammingSubmissionService programmingSubmissionService) { + ProgrammingSubmissionService programmingSubmissionService, IrisSettingsService irisSettingsService) { this.programmingExerciseRepository = programmingExerciseRepository; this.gitService = gitService; this.versionControlService = versionControlService; @@ -168,6 +171,7 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc this.programmingLanguageFeatureService = programmingLanguageFeatureService; this.channelService = channelService; this.programmingSubmissionService = programmingSubmissionService; + this.irisSettingsService = irisSettingsService; } /** @@ -639,6 +643,8 @@ public void delete(Long programmingExerciseId, boolean deleteBaseReposBuildPlans programmingExerciseGitDiffReportRepository.deleteByProgrammingExerciseId(programmingExerciseId); + irisSettingsService.deleteSettingsFor(programmingExercise); + SolutionProgrammingExerciseParticipation solutionProgrammingExerciseParticipation = programmingExercise.getSolutionParticipation(); TemplateProgrammingExerciseParticipation templateProgrammingExerciseParticipation = programmingExercise.getTemplateParticipation(); if (solutionProgrammingExerciseParticipation != null) { From 33fae3322078db6588ea0161315d3f49c35e1a2f Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Wed, 22 Nov 2023 14:24:50 +0100 Subject: [PATCH 29/30] Fix if Iris profile is not present --- .../tum/in/www1/artemis/service/CourseService.java | 6 +++--- .../programming/ProgrammingExerciseService.java | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java index 589d5edacff3..d08b534043fe 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java @@ -155,7 +155,7 @@ public class CourseService { private final LearningPathService learningPathService; - private final IrisSettingsService irisSettingsService; + private final Optional irisSettingsService; public CourseService(Environment env, ArtemisAuthenticationProvider artemisAuthenticationProvider, CourseRepository courseRepository, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService, AuthorizationCheckService authCheckService, UserRepository userRepository, LectureService lectureService, @@ -168,7 +168,7 @@ public CourseService(Environment env, ArtemisAuthenticationProvider artemisAuthe ExerciseRepository exerciseRepository, ParticipantScoreRepository participantScoreRepository, PresentationPointsCalculationService presentationPointsCalculationService, TutorialGroupRepository tutorialGroupRepository, TutorialGroupService tutorialGroupService, TutorialGroupsConfigurationRepository tutorialGroupsConfigurationRepository, PlagiarismCaseRepository plagiarismCaseRepository, ConversationRepository conversationRepository, LearningPathService learningPathService, - IrisSettingsService irisSettingsService) { + Optional irisSettingsService) { this.env = env; this.artemisAuthenticationProvider = artemisAuthenticationProvider; this.courseRepository = courseRepository; @@ -378,7 +378,7 @@ public void delete(Course course) { deleteExamsOfCourse(course); deleteGradingScaleOfCourse(course); deleteTutorialGroupsOfCourse(course); - irisSettingsService.deleteSettingsFor(course); + irisSettingsService.ifPresent(iss -> iss.deleteSettingsFor(course)); courseRepository.deleteById(course.getId()); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java index 5c7a6029bcb4..9da4f181ffe5 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java @@ -131,7 +131,7 @@ public class ProgrammingExerciseService { private final ProgrammingSubmissionService programmingSubmissionService; - private final IrisSettingsService irisSettingsService; + private final Optional irisSettingsService; public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerciseRepository, GitService gitService, Optional versionControlService, Optional continuousIntegrationService, Optional continuousIntegrationTriggerService, @@ -144,7 +144,7 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository, ExerciseSpecificationService exerciseSpecificationService, ProgrammingExerciseRepositoryService programmingExerciseRepositoryService, AuxiliaryRepositoryService auxiliaryRepositoryService, SubmissionPolicyService submissionPolicyService, Optional programmingLanguageFeatureService, ChannelService channelService, - ProgrammingSubmissionService programmingSubmissionService, IrisSettingsService irisSettingsService) { + ProgrammingSubmissionService programmingSubmissionService, Optional irisSettingsService) { this.programmingExerciseRepository = programmingExerciseRepository; this.gitService = gitService; this.versionControlService = versionControlService; @@ -627,7 +627,7 @@ private boolean saveAndPushStructuralOracle(User user, Repository testRepository public void delete(Long programmingExerciseId, boolean deleteBaseReposBuildPlans) { // Note: This method does not accept a programming exercise to solve issues with nested Transactions. // It would be good to refactor the delete calls and move the validity checks down from the resources to the service methods (e.g. EntityNotFound). - var programmingExercise = programmingExerciseRepository.findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExerciseId) + final var programmingExercise = programmingExerciseRepository.findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExerciseId) .orElseThrow(() -> new EntityNotFoundException("Programming Exercise", programmingExerciseId)); // The delete operation cancels scheduled tasks (like locking/unlocking repositories) @@ -643,7 +643,7 @@ public void delete(Long programmingExerciseId, boolean deleteBaseReposBuildPlans programmingExerciseGitDiffReportRepository.deleteByProgrammingExerciseId(programmingExerciseId); - irisSettingsService.deleteSettingsFor(programmingExercise); + irisSettingsService.ifPresent(iss -> iss.deleteSettingsFor(programmingExercise)); SolutionProgrammingExerciseParticipation solutionProgrammingExerciseParticipation = programmingExercise.getSolutionParticipation(); TemplateProgrammingExerciseParticipation templateProgrammingExerciseParticipation = programmingExercise.getTemplateParticipation(); @@ -655,8 +655,8 @@ public void delete(Long programmingExerciseId, boolean deleteBaseReposBuildPlans } // Note: we fetch the programming exercise again here with student participations to avoid Hibernate issues during the delete operation below - programmingExercise = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(programmingExerciseId); - log.debug("Delete programming exercises with student participations: {}", programmingExercise.getStudentParticipations()); + var programmingExerciseWithStudentParticipations = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(programmingExerciseId); + log.debug("Delete programming exercises with student participations: {}", programmingExerciseWithStudentParticipations.getStudentParticipations()); // This will also delete the template & solution participation: we explicitly use deleteById to avoid potential Hibernate issues during deletion programmingExerciseRepository.deleteById(programmingExerciseId); } From f720a4ded0c049b17d55d20ca7d660ab90b8f716 Mon Sep 17 00:00:00 2001 From: Timor Morrien Date: Wed, 22 Nov 2023 23:27:35 +0100 Subject: [PATCH 30/30] Add specific buttons to enable/disable Iris features on the course and exercise detail pages. --- .../iris/settings/IrisSubSettingsService.java | 4 +- .../web/rest/iris/IrisSettingsResource.java | 4 +- .../course/manage/course-management.module.ts | 2 + .../detail/course-detail.component.html | 20 ++++++ .../manage/detail/course-detail.component.ts | 24 ++++++- ...programming-exercise-detail.component.html | 10 ++- .../programming-exercise-detail.component.ts | 10 +++ .../programming-exercise-management.module.ts | 2 + src/main/webapp/app/iris/iris.module.ts | 4 +- ...exercise-settings-update-routing.module.ts | 2 +- .../shared/iris-enabled.component.html | 18 +++++ .../settings/shared/iris-enabled.component.ts | 68 +++++++++++++++++++ src/main/webapp/i18n/de/iris.json | 7 ++ src/main/webapp/i18n/en/iris.json | 7 ++ 14 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 src/main/webapp/app/iris/settings/shared/iris-enabled.component.html create mode 100644 src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts diff --git a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java index b5bd921e697e..2641c8eb34f6 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/iris/settings/IrisSubSettingsService.java @@ -170,10 +170,10 @@ private String validatePreferredModel(String preferredModel, String newPreferred else if (authCheckService.isAdmin()) { return newPreferredModel; } - else if (allowedModels != null && allowedModels.contains(newPreferredModel)) { + else if (allowedModels != null && !allowedModels.isEmpty() && allowedModels.contains(newPreferredModel)) { return newPreferredModel; } - else if (allowedModels == null && parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)) { + else if ((allowedModels == null || allowedModels.isEmpty()) && parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)) { return newPreferredModel; } else { diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java index b65efaef8694..ff3460c2df41 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/iris/IrisSettingsResource.java @@ -151,11 +151,11 @@ public ResponseEntity updateCourseSettings(@PathVariable Lon * found. */ @PutMapping("programming-exercises/{exerciseId}/raw-iris-settings") - @EnforceAtLeastEditor + @EnforceAtLeastInstructor public ResponseEntity updateProgrammingExerciseSettings(@PathVariable Long exerciseId, @RequestBody IrisExerciseSettings settings) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); var user = userRepository.getUserWithGroupsAndAuthorities(); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, user); + authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, exercise, user); settings.setExercise(exercise); var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); diff --git a/src/main/webapp/app/course/manage/course-management.module.ts b/src/main/webapp/app/course/manage/course-management.module.ts index 08a645469e4c..4a03b5230da3 100644 --- a/src/main/webapp/app/course/manage/course-management.module.ts +++ b/src/main/webapp/app/course/manage/course-management.module.ts @@ -64,6 +64,7 @@ import { ExerciseCategoriesModule } from 'app/shared/exercise-categories/exercis import { CourseManagementTabBarComponent } from 'app/course/manage/course-management-tab-bar/course-management-tab-bar.component'; import { ArtemisExerciseCreateButtonsModule } from 'app/exercises/shared/manage/exercise-create-buttons.module'; import { ArtemisLearningPathManagementModule } from 'app/course/learning-paths/learning-path-management/learning-path-management.module'; +import { IrisModule } from 'app/iris/iris.module'; @NgModule({ imports: [ @@ -115,6 +116,7 @@ import { ArtemisLearningPathManagementModule } from 'app/course/learning-paths/l NgbNavModule, ArtemisExerciseCreateButtonsModule, ArtemisLearningPathManagementModule, + IrisModule, ], declarations: [ CourseManagementComponent, diff --git a/src/main/webapp/app/course/manage/detail/course-detail.component.html b/src/main/webapp/app/course/manage/detail/course-detail.component.html index 469a639f026d..2b3f5c0d3fe0 100644 --- a/src/main/webapp/app/course/manage/detail/course-detail.component.html +++ b/src/main/webapp/app/course/manage/detail/course-detail.component.html @@ -238,6 +238,26 @@

Course Details: +
+
Iris Chat
+
+ +
+
+ +
+
Iris Hestia
+
+ +
+
+ +
+
Iris CodeEditor
+
+ +
+

diff --git a/src/main/webapp/app/course/manage/detail/course-detail.component.ts b/src/main/webapp/app/course/manage/detail/course-detail.component.ts index aadf18cc563c..522ff025c1a5 100644 --- a/src/main/webapp/app/course/manage/detail/course-detail.component.ts +++ b/src/main/webapp/app/course/manage/detail/course-detail.component.ts @@ -13,6 +13,9 @@ import { EventManager } from 'app/core/util/event-manager.service'; import { faChartBar, faClipboard, faEye, faFlag, faListAlt, faTable, faTimes, faWrench } from '@fortawesome/free-solid-svg-icons'; import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; import { OrganizationManagementService } from 'app/admin/organization-management/organization-management.service'; +import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; +import { AccountService } from 'app/core/auth/account.service'; export enum DoughnutChartType { ASSESSMENT = 'ASSESSMENT', @@ -32,6 +35,9 @@ export enum DoughnutChartType { export class CourseDetailComponent implements OnInit, OnDestroy { readonly DoughnutChartType = DoughnutChartType; readonly FeatureToggle = FeatureToggle; + readonly CHAT = IrisSubSettingsType.CHAT; + readonly HESTIA = IrisSubSettingsType.HESTIA; + readonly CODE_EDITOR = IrisSubSettingsType.CODE_EDITOR; courseDTO: CourseManagementDetailViewDto; activeStudents?: number[]; @@ -39,9 +45,14 @@ export class CourseDetailComponent implements OnInit, OnDestroy { messagingEnabled: boolean; communicationEnabled: boolean; - + irisEnabled = false; + irisChatEnabled = false; + irisHestiaEnabled = false; + irisCodeEditorEnabled = false; ltiEnabled = false; + isAdmin = false; + private eventSubscriber: Subscription; paramSub: Subscription; @@ -62,6 +73,8 @@ export class CourseDetailComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private alertService: AlertService, private profileService: ProfileService, + private accountService: AccountService, + private irisSettingsService: IrisSettingsService, ) {} /** @@ -70,6 +83,14 @@ export class CourseDetailComponent implements OnInit, OnDestroy { ngOnInit() { this.profileService.getProfileInfo().subscribe((profileInfo) => { this.ltiEnabled = profileInfo.activeProfiles.includes(PROFILE_LTI); + this.irisEnabled = profileInfo.activeProfiles.includes('iris'); + if (this.irisEnabled) { + this.irisSettingsService.getGlobalSettings().subscribe((settings) => { + this.irisChatEnabled = settings?.irisChatSettings?.enabled ?? false; + this.irisHestiaEnabled = settings?.irisHestiaSettings?.enabled ?? false; + this.irisCodeEditorEnabled = settings?.irisCodeEditorSettings?.enabled ?? false; + }); + } }); this.route.data.subscribe(({ course }) => { if (course) { @@ -77,6 +98,7 @@ export class CourseDetailComponent implements OnInit, OnDestroy { this.messagingEnabled = !!this.course.courseInformationSharingConfiguration?.includes('MESSAGING'); this.communicationEnabled = !!this.course.courseInformationSharingConfiguration?.includes('COMMUNICATION'); } + this.isAdmin = this.accountService.isAdmin(); }); // There is no course 0 -> will fetch no course if route does not provide different courseId let courseId = 0; diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html index 25fff457695d..04ce4af2f563 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html @@ -61,7 +61,7 @@

Programming Exercise Details

Show Submissions + +
+
Iris Chat
+
+ +
+
+
Lines added/removed between template and solution
diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts index 16b008e575cd..23169a615632 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts @@ -54,6 +54,8 @@ import { DocumentationType } from 'app/shared/components/documentation-button/do import { ConsistencyCheckService } from 'app/shared/consistency-check/consistency-check.service'; import { hasEditableBuildPlan } from 'app/shared/layouts/profiles/profile-info.model'; import { PROFILE_LOCALVC } from 'app/app.constants'; +import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; @Component({ selector: 'jhi-programming-exercise-detail', @@ -71,6 +73,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { readonly ButtonSize = ButtonSize; readonly AssessmentType = AssessmentType; readonly documentationType: DocumentationType = 'Programming'; + readonly CHAT = IrisSubSettingsType.CHAT; programmingExercise: ProgrammingExercise; isExamExercise: boolean; @@ -87,6 +90,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { // Also used to hide the buttons to lock and unlock all repositories as that does not do anything in the local VCS. localVCEnabled = false; irisEnabled = false; + irisChatEnabled = false; isAdmin = false; addedLineCount: number; @@ -137,6 +141,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { private router: Router, private programmingLanguageFeatureService: ProgrammingLanguageFeatureService, private consistencyCheckService: ConsistencyCheckService, + private irisSettingsService: IrisSettingsService, ) {} ngOnInit() { @@ -190,6 +195,11 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { this.programmingLanguageFeatureService.getProgrammingLanguageFeature(programmingExercise.programmingLanguage).auxiliaryRepositoriesSupported ?? false; this.localVCEnabled = profileInfo.activeProfiles.includes(PROFILE_LOCALVC); this.irisEnabled = profileInfo.activeProfiles.includes('iris'); + if (this.irisEnabled) { + this.irisSettingsService.getCombinedCourseSettings(this.courseId).subscribe((settings) => { + this.irisChatEnabled = settings?.irisChatSettings?.enabled ?? false; + }); + } } }); diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts index 9d774a5e1f3d..396ad0155ec0 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts @@ -28,6 +28,7 @@ import { ArtemisCodeHintGenerationOverviewModule } from 'app/exercises/programmi import { BuildPlanEditorComponent } from 'app/exercises/programming/manage/build-plan-editor.component'; import { AceEditorModule } from 'app/shared/markdown-editor/ace-editor/ace-editor.module'; import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-editor/code-editor.module'; +import { IrisModule } from 'app/iris/iris.module'; @NgModule({ imports: [ @@ -54,6 +55,7 @@ import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-e ArtemisCodeHintGenerationOverviewModule, AceEditorModule, ArtemisCodeEditorModule, + IrisModule, ], declarations: [ ProgrammingExerciseDetailComponent, diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index f31ed834cbed..4bc954df4004 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -20,6 +20,7 @@ import { IrisChatSubSettingsUpdateComponent } from 'app/iris/settings/iris-setti import { IrisHestiaSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-hestia-sub-settings-update/iris-hestia-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from './settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; import { IrisCodeEditorSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-code-editor-sub-settings-update/iris-code-editor-sub-settings-update.component'; +import { IrisEnabledComponent } from 'app/iris/settings/shared/iris-enabled.component'; @NgModule({ declarations: [ @@ -36,9 +37,10 @@ import { IrisCodeEditorSubSettingsUpdateComponent } from 'app/iris/settings/iris IrisHestiaSubSettingsUpdateComponent, IrisGlobalAutoupdateSettingsUpdateComponent, IrisCodeEditorSubSettingsUpdateComponent, + IrisEnabledComponent, ], imports: [CommonModule, MatDialogModule, FormsModule, FontAwesomeModule, ArtemisSharedModule, ArtemisMarkdownModule, ArtemisSharedComponentModule, RouterModule], providers: [], - exports: [ExerciseChatbotComponent], + exports: [ExerciseChatbotComponent, IrisEnabledComponent], }) export class IrisModule {} diff --git a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update-routing.module.ts b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update-routing.module.ts index c2eedcd050d4..4b7253190157 100644 --- a/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update-routing.module.ts +++ b/src/main/webapp/app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update-routing.module.ts @@ -11,7 +11,7 @@ const routes: Routes = [ path: '', component: IrisExerciseSettingsUpdateComponent, data: { - authorities: [Authority.EDITOR, Authority.INSTRUCTOR, Authority.ADMIN], + authorities: [Authority.INSTRUCTOR, Authority.ADMIN], pageTitle: 'artemisApp.iris.settings.title.exercise', }, canActivate: [UserRouteAccessService], diff --git a/src/main/webapp/app/iris/settings/shared/iris-enabled.component.html b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.html new file mode 100644 index 000000000000..17d26de15f89 --- /dev/null +++ b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.html @@ -0,0 +1,18 @@ +
+
+ {{ 'artemisApp.iris.settings.subSettings.enabled.on' | artemisTranslate }} +
+
+ {{ 'artemisApp.iris.settings.subSettings.enabled.off' | artemisTranslate }} +
+
diff --git a/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts new file mode 100644 index 000000000000..26e480f31198 --- /dev/null +++ b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts @@ -0,0 +1,68 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { IrisSubSettings, IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { Exercise } from 'app/entities/exercise.model'; +import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; +import { Course } from 'app/entities/course.model'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; + +@Component({ + selector: 'jhi-iris-enabled', + templateUrl: './iris-enabled.component.html', +}) +export class IrisEnabledComponent implements OnInit { + @Input() exercise?: Exercise; + @Input() course?: Course; + @Input() irisSubSettingsType: IrisSubSettingsType; + @Input() disabled? = false; + + irisSettings?: IrisSettings; + irisSubSettings?: IrisSubSettings; + + constructor(private irisSettingsService: IrisSettingsService) {} + + ngOnInit(): void { + console.log(this.exercise, this.course, this.irisSubSettingsType); + if (this.exercise) { + this.irisSettingsService.getUncombinedProgrammingExerciseSettings(this.exercise.id!).subscribe((settings) => { + this.irisSettings = settings; + this.setSubSettings(); + }); + } else if (this.course) { + this.irisSettingsService.getUncombinedCourseSettings(this.course.id!).subscribe((settings) => { + this.irisSettings = settings; + this.setSubSettings(); + }); + } + } + + setEnabled(enabled: boolean) { + if (!this.disabled && this.irisSubSettings) { + this.irisSubSettings.enabled = enabled; + if (this.exercise) { + this.irisSettingsService.setProgrammingExerciseSettings(this.exercise.id!, this.irisSettings!).subscribe((response) => { + this.irisSettings = response.body ?? this.irisSettings; + this.setSubSettings(); + }); + } else if (this.course) { + this.irisSettingsService.setCourseSettings(this.course.id!, this.irisSettings!).subscribe((response) => { + this.irisSettings = response.body ?? this.irisSettings; + this.setSubSettings(); + }); + } + } + } + + private setSubSettings() { + switch (this.irisSubSettingsType) { + case IrisSubSettingsType.CHAT: + this.irisSubSettings = this.irisSettings?.irisChatSettings; + break; + case IrisSubSettingsType.HESTIA: + this.irisSubSettings = this.irisSettings?.irisHestiaSettings; + break; + case IrisSubSettingsType.CODE_EDITOR: + this.irisSubSettings = this.irisSettings?.irisCodeEditorSettings; + break; + } + } +} diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index 2e5e6d2ae9e8..c6d4524e917c 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -51,6 +51,13 @@ "templateRepoTemplate": "Vorlagen Repository Generations Template", "solutionRepoTemplate": "Lösungs Repository Generations Template", "testRepoTemplate": "Test Repository Generations Template" + }, + "enabled": { + "on": "Aktiviert", + "off": "Deaktiviert", + "chat": "Iris Chat", + "codeEditor": "Programmieraufgaben Erstellungs Chat", + "hestia": "Hestia Integration" } }, "title": { diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index 17551ebb3738..29e18750e4e2 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -51,6 +51,13 @@ "templateRepoTemplate": "Template Repository Generation Template", "solutionRepoTemplate": "Solution Repository Generation Template", "testRepoTemplate": "Test Repository Generation Template" + }, + "enabled": { + "on": "Enabled", + "off": "Disabled", + "chat": "Iris Chat", + "codeEditor": "Programming Exercise Creation Chat", + "hestia": "Hestia Integration" } }, "title": {